Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added an example using actix-web, async_graphql and diesel #538

Merged
merged 13 commits into from
Feb 28, 2024
4 changes: 4 additions & 0 deletions examples/web_app_graphql/.env.example.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DATABASE_URL=postgres://[username]:[password]@localhost/[database_name]
MEILISEARCH_HOST=http://localhost:7700
MEILISEARCH_API_KEY=[your-master-key]
MIGRATIONS_DIR_PATH=migrations
4 changes: 4 additions & 0 deletions examples/web_app_graphql/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/target
/Cargo.lock

.env
26 changes: 26 additions & 0 deletions examples/web_app_graphql/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "meilisearch-ex"
version = "0.1.0"
edition = "2021"
authors = ["Eugene Korir <[email protected]>"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-cors = "0.6.5"
actix-web = "4.4.0"
async-graphql = "6.0.11"
async-graphql-actix-web = "6.0.11"
diesel = { version = "2.1.4", features = ["postgres"] }
diesel-async = { version = "0.4.1", features = ["postgres", "deadpool"] }
diesel_migrations = "2.1.0"
dotenvy = "0.15.7"
env_logger = "0.10.1"
envy = "0.4.2"
futures = "0.3.29"
log = "0.4.20"
meilisearch-sdk = "0.24.3"
serde = { version = "1.0.192", features = ["derive"] }
serde_json = "1.0.108"
thiserror = "1.0.51"
validator = { version = "0.16.1", features = ["derive"] }
60 changes: 60 additions & 0 deletions examples/web_app_graphql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Meilisearch example with graphql using `diesel`, `async_graphql` and `postgres`

## Contents

Setting up a graphql server using `async_graphql` and `actix-web`

Using `diesel` to query the database

Using `meilisearch-sdk` to search for records that match a given criteria

## Running the example

The meilisearch server needs to be running. You can run it by the command below

```bash
meilisearch --master-key <your master key>
```

Then you can run the application by simply running

```bash
cargo run --release
```

The above command will display a link to your running instance and you can simply proceed by clicking the link or navigating to your browser.

### Running the resolvers

On your browser, you will see a graphql playground in which you can use to run some queries

You can use the `searchUsers` query as follows:

```gpl
query {
users{
search(queryString: "Eugene"){
lastName
firstName
email
}
}
}
```

### Errors

Incase you run into the following error:

```bash
= note: ld: library not found for -lpq
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```

Run:

```bash
sudo apt install libpq-dev
```

This should fix the error
9 changes: 9 additions & 0 deletions examples/web_app_graphql/diesel.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# For documentation on how to configure this file,
# see https://diesel.rs/guides/configuring-diesel-cli

[print_schema]
file = "src/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId"]

[migrations_directory]
dir = "migrations"
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.

DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.




-- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns)
--
-- # Example
--
-- ```sql
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
--
-- SELECT diesel_manage_updated_at('users');
-- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
BEGIN
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table if exists users;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Your SQL goes here
create table if not exists users(
id serial primary key,
first_name varchar not null,
last_name varchar not null,
email varchar not null unique
);
10 changes: 10 additions & 0 deletions examples/web_app_graphql/src/app_env_vars.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use serde::Deserialize;

//Environment variables required for the app to run
#[derive(Deserialize, Debug, Clone)]
pub struct AppEnvVars {
pub meilisearch_api_key: String,
pub meilisearch_host: String,
pub database_url: String,
pub migrations_dir_path: String,
}
25 changes: 25 additions & 0 deletions examples/web_app_graphql/src/errors/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use diesel::ConnectionError;
use diesel_async::pooled_connection::deadpool::{BuildError, PoolError};
use diesel_migrations::MigrationError;
use serde_json::Error as SerdeError;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ApplicationError {
#[error("Missing environment variable")]
Envy(#[from] envy::Error),
#[error("Input/Output error")]
Io(#[from] std::io::Error),
#[error("Database error")]
Diesel(#[from] diesel::result::Error),
#[error("Deadpool build error")]
DeadpoolBuild(#[from] BuildError),
#[error("Migration error")]
Migration(#[from] MigrationError),
#[error("Connection error")]
DieselConnection(#[from] ConnectionError),
#[error("Pool Error")]
Pool(#[from] PoolError),
#[error("Serde json error")]
SerDe(#[from] SerdeError),
}
15 changes: 15 additions & 0 deletions examples/web_app_graphql/src/graphql_schema/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use async_graphql::SimpleObject;
pub mod users;

use users::mutation::UsersMut;
use users::query::UsersQuery;

#[derive(Default, SimpleObject)]
pub struct Query {
users: UsersQuery,
}

#[derive(Default, SimpleObject)]
pub struct Mutation {
users: UsersMut,
}
2 changes: 2 additions & 0 deletions examples/web_app_graphql/src/graphql_schema/users/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod mutation;
pub mod query;
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use async_graphql::{Context, InputObject, Object, Result};
use diesel_async::RunQueryDsl;
use validator::Validate;

use crate::{
models::{NewUser, User},
validate_input, GraphQlData,
};

#[derive(Default)]
pub struct AddUser;

#[derive(InputObject, Validate)]
pub struct IAddUser {
#[validate(length(min = 1))]
pub first_name: String,
#[validate(length(min = 1))]
pub last_name: String,
#[validate(email)]
pub email: String,
}

#[Object]
impl AddUser {
///Resolver for creating a new user and storing that data in the database
///
/// The mutation can be run as follows
/// ```gpl
/// mutation AddUser{
/// users {
/// signup(input: {firstName: "",lastName: "",email: ""}){
/// id
/// firstName
/// lastName
/// email
/// }
/// }
/// }
pub async fn signup(&self, ctx: &Context<'_>, input: IAddUser) -> Result<User> {
validate_input(&input)?;

use crate::schema::users::dsl::users;

let GraphQlData { pool, .. } = ctx.data().map_err(|e| {
log::error!("Failed to get app data: {:?}", e);
e
})?;

let mut connection = pool.get().await?;

let value = NewUser {
first_name: input.first_name,
last_name: input.last_name,
email: input.email,
};

let result = diesel::insert_into(users)
.values(&value)
.get_result::<User>(&mut connection)
.await
.map_err(|e| {
log::error!("Could not create new user: {:#?}", e);
e
})?;

Ok(result)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub mod add_user;

use add_user::AddUser;
use async_graphql::MergedObject;

//Combines user queries into one struct
#[derive(Default, MergedObject)]
pub struct UsersMut(pub AddUser);
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use async_graphql::{Context, Object, Result};
use diesel_async::RunQueryDsl;

use crate::{models::User, GraphQlData};

#[derive(Default)]
pub struct GetUsers;

#[Object]
impl GetUsers {
//Resolver for querying the database for user records
pub async fn get_users(&self, ctx: &Context<'_>) -> Result<Vec<User>> {
use crate::schema::users::dsl::users;

let GraphQlData { pool, .. } = ctx.data().map_err(|e| {
log::error!("Failed to get app data: {:?}", e);
e
})?;

let mut connection = pool.get().await?;

let list_users = users.load::<User>(&mut connection).await?;

Ok(list_users)
}
}
10 changes: 10 additions & 0 deletions examples/web_app_graphql/src/graphql_schema/users/query/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pub mod get_users;
pub mod search;

use async_graphql::MergedObject;
use get_users::GetUsers;
use search::SearchUsers;

//Combines user queries into one struct
#[derive(Default, MergedObject)]
pub struct UsersQuery(pub GetUsers, pub SearchUsers);
Loading
Loading