Skip to content

Commit 1774889

Browse files
Merge #538
538: Added an example using actix-web, async_graphql and diesel r=irevoire a=korir248 # Pull Request ## Related issue Fixes #247 ## What does this PR do? - Adds an example which depivts using `actix-web` and `async_graphql` to query records in a `postgres` database ## PR checklist Please check if your PR fulfills the following requirements: - [x] Does this PR fix an existing issue, or have you listed the changes applied in the PR description (and why they are needed)? - [x] Have you read the contributing guidelines? - [x] Have you made sure that the title is accurate and descriptive of the changes? Thank you so much for contributing to Meilisearch! Co-authored-by: Eugene Korir <[email protected]> Co-authored-by: Eugene Korir <[email protected]>
2 parents f7d4b3c + 2afbd38 commit 1774889

File tree

23 files changed

+540
-0
lines changed

23 files changed

+540
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
DATABASE_URL=postgres://[username]:[password]@localhost/[database_name]
2+
MEILISEARCH_HOST=http://localhost:7700
3+
MEILISEARCH_API_KEY=[your-master-key]
4+
MIGRATIONS_DIR_PATH=migrations

examples/web_app_graphql/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/target
2+
/Cargo.lock
3+
4+
.env

examples/web_app_graphql/Cargo.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "meilisearch-ex"
3+
version = "0.1.0"
4+
edition = "2021"
5+
authors = ["Eugene Korir <[email protected]>"]
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
actix-cors = "0.6.5"
11+
actix-web = "4.4.0"
12+
async-graphql = "6.0.11"
13+
async-graphql-actix-web = "6.0.11"
14+
diesel = { version = "2.1.4", features = ["postgres"] }
15+
diesel-async = { version = "0.4.1", features = ["postgres", "deadpool"] }
16+
diesel_migrations = "2.1.0"
17+
dotenvy = "0.15.7"
18+
env_logger = "0.10.1"
19+
envy = "0.4.2"
20+
futures = "0.3.29"
21+
log = "0.4.20"
22+
meilisearch-sdk = "0.24.3"
23+
serde = { version = "1.0.192", features = ["derive"] }
24+
serde_json = "1.0.108"
25+
thiserror = "1.0.51"
26+
validator = { version = "0.16.1", features = ["derive"] }

examples/web_app_graphql/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Meilisearch example with graphql using `diesel`, `async_graphql` and `postgres`
2+
3+
## Contents
4+
5+
Setting up a graphql server using `async_graphql` and `actix-web`
6+
7+
Using `diesel` to query the database
8+
9+
Using `meilisearch-sdk` to search for records that match a given criteria
10+
11+
## Running the example
12+
13+
The meilisearch server needs to be running. You can run it by the command below
14+
15+
```bash
16+
meilisearch --master-key <your master key>
17+
```
18+
19+
Then you can run the application by simply running
20+
21+
```bash
22+
cargo run --release
23+
```
24+
25+
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.
26+
27+
### Running the resolvers
28+
29+
On your browser, you will see a graphql playground in which you can use to run some queries
30+
31+
You can use the `searchUsers` query as follows:
32+
33+
```gpl
34+
query {
35+
users{
36+
search(queryString: "Eugene"){
37+
lastName
38+
firstName
39+
email
40+
}
41+
}
42+
}
43+
```
44+
45+
### Errors
46+
47+
Incase you run into the following error:
48+
49+
```bash
50+
= note: ld: library not found for -lpq
51+
clang: error: linker command failed with exit code 1 (use -v to see invocation)
52+
```
53+
54+
Run:
55+
56+
```bash
57+
sudo apt install libpq-dev
58+
```
59+
60+
This should fix the error

examples/web_app_graphql/diesel.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# For documentation on how to configure this file,
2+
# see https://diesel.rs/guides/configuring-diesel-cli
3+
4+
[print_schema]
5+
file = "src/schema.rs"
6+
custom_type_derives = ["diesel::query_builder::QueryId"]
7+
8+
[migrations_directory]
9+
dir = "migrations"

examples/web_app_graphql/migrations/.keep

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- This file was automatically created by Diesel to setup helper functions
2+
-- and other internal bookkeeping. This file is safe to edit, any future
3+
-- changes will be added to existing projects as new migrations.
4+
5+
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
6+
DROP FUNCTION IF EXISTS diesel_set_updated_at();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
-- This file was automatically created by Diesel to setup helper functions
2+
-- and other internal bookkeeping. This file is safe to edit, any future
3+
-- changes will be added to existing projects as new migrations.
4+
5+
6+
7+
8+
-- Sets up a trigger for the given table to automatically set a column called
9+
-- `updated_at` whenever the row is modified (unless `updated_at` was included
10+
-- in the modified columns)
11+
--
12+
-- # Example
13+
--
14+
-- ```sql
15+
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
16+
--
17+
-- SELECT diesel_manage_updated_at('users');
18+
-- ```
19+
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
20+
BEGIN
21+
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
22+
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
23+
END;
24+
$$ LANGUAGE plpgsql;
25+
26+
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
27+
BEGIN
28+
IF (
29+
NEW IS DISTINCT FROM OLD AND
30+
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
31+
) THEN
32+
NEW.updated_at := current_timestamp;
33+
END IF;
34+
RETURN NEW;
35+
END;
36+
$$ LANGUAGE plpgsql;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- This file should undo anything in `up.sql`
2+
drop table if exists users;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Your SQL goes here
2+
create table if not exists users(
3+
id serial primary key,
4+
first_name varchar not null,
5+
last_name varchar not null,
6+
email varchar not null unique
7+
);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use serde::Deserialize;
2+
3+
//Environment variables required for the app to run
4+
#[derive(Deserialize, Debug, Clone)]
5+
pub struct AppEnvVars {
6+
pub meilisearch_api_key: String,
7+
pub meilisearch_host: String,
8+
pub database_url: String,
9+
pub migrations_dir_path: String,
10+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use diesel::ConnectionError;
2+
use diesel_async::pooled_connection::deadpool::{BuildError, PoolError};
3+
use diesel_migrations::MigrationError;
4+
use serde_json::Error as SerdeError;
5+
use thiserror::Error;
6+
7+
#[derive(Debug, Error)]
8+
pub enum ApplicationError {
9+
#[error("Missing environment variable")]
10+
Envy(#[from] envy::Error),
11+
#[error("Input/Output error")]
12+
Io(#[from] std::io::Error),
13+
#[error("Database error")]
14+
Diesel(#[from] diesel::result::Error),
15+
#[error("Deadpool build error")]
16+
DeadpoolBuild(#[from] BuildError),
17+
#[error("Migration error")]
18+
Migration(#[from] MigrationError),
19+
#[error("Connection error")]
20+
DieselConnection(#[from] ConnectionError),
21+
#[error("Pool Error")]
22+
Pool(#[from] PoolError),
23+
#[error("Serde json error")]
24+
SerDe(#[from] SerdeError),
25+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use async_graphql::SimpleObject;
2+
pub mod users;
3+
4+
use users::mutation::UsersMut;
5+
use users::query::UsersQuery;
6+
7+
#[derive(Default, SimpleObject)]
8+
pub struct Query {
9+
users: UsersQuery,
10+
}
11+
12+
#[derive(Default, SimpleObject)]
13+
pub struct Mutation {
14+
users: UsersMut,
15+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod mutation;
2+
pub mod query;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use async_graphql::{Context, InputObject, Object, Result};
2+
use diesel_async::RunQueryDsl;
3+
use validator::Validate;
4+
5+
use crate::{
6+
models::{NewUser, User},
7+
validate_input, GraphQlData,
8+
};
9+
10+
#[derive(Default)]
11+
pub struct AddUser;
12+
13+
#[derive(InputObject, Validate)]
14+
pub struct IAddUser {
15+
#[validate(length(min = 1))]
16+
pub first_name: String,
17+
#[validate(length(min = 1))]
18+
pub last_name: String,
19+
#[validate(email)]
20+
pub email: String,
21+
}
22+
23+
#[Object]
24+
impl AddUser {
25+
///Resolver for creating a new user and storing that data in the database
26+
///
27+
/// The mutation can be run as follows
28+
/// ```gpl
29+
/// mutation AddUser{
30+
/// users {
31+
/// signup(input: {firstName: "",lastName: "",email: ""}){
32+
/// id
33+
/// firstName
34+
/// lastName
35+
/// email
36+
/// }
37+
/// }
38+
/// }
39+
pub async fn signup(&self, ctx: &Context<'_>, input: IAddUser) -> Result<User> {
40+
validate_input(&input)?;
41+
42+
use crate::schema::users::dsl::users;
43+
44+
let GraphQlData { pool, .. } = ctx.data().map_err(|e| {
45+
log::error!("Failed to get app data: {:?}", e);
46+
e
47+
})?;
48+
49+
let mut connection = pool.get().await?;
50+
51+
let value = NewUser {
52+
first_name: input.first_name,
53+
last_name: input.last_name,
54+
email: input.email,
55+
};
56+
57+
let result = diesel::insert_into(users)
58+
.values(&value)
59+
.get_result::<User>(&mut connection)
60+
.await
61+
.map_err(|e| {
62+
log::error!("Could not create new user: {:#?}", e);
63+
e
64+
})?;
65+
66+
Ok(result)
67+
}
68+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pub mod add_user;
2+
3+
use add_user::AddUser;
4+
use async_graphql::MergedObject;
5+
6+
//Combines user queries into one struct
7+
#[derive(Default, MergedObject)]
8+
pub struct UsersMut(pub AddUser);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use async_graphql::{Context, Object, Result};
2+
use diesel_async::RunQueryDsl;
3+
4+
use crate::{models::User, GraphQlData};
5+
6+
#[derive(Default)]
7+
pub struct GetUsers;
8+
9+
#[Object]
10+
impl GetUsers {
11+
//Resolver for querying the database for user records
12+
pub async fn get_users(&self, ctx: &Context<'_>) -> Result<Vec<User>> {
13+
use crate::schema::users::dsl::users;
14+
15+
let GraphQlData { pool, .. } = ctx.data().map_err(|e| {
16+
log::error!("Failed to get app data: {:?}", e);
17+
e
18+
})?;
19+
20+
let mut connection = pool.get().await?;
21+
22+
let list_users = users.load::<User>(&mut connection).await?;
23+
24+
Ok(list_users)
25+
}
26+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
pub mod get_users;
2+
pub mod search;
3+
4+
use async_graphql::MergedObject;
5+
use get_users::GetUsers;
6+
use search::SearchUsers;
7+
8+
//Combines user queries into one struct
9+
#[derive(Default, MergedObject)]
10+
pub struct UsersQuery(pub GetUsers, pub SearchUsers);

0 commit comments

Comments
 (0)