Skip to content

Commit

Permalink
Merge #538
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
3 people authored Feb 28, 2024
2 parents f7d4b3c + 2afbd38 commit 1774889
Show file tree
Hide file tree
Showing 23 changed files with 540 additions and 0 deletions.
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

0 comments on commit 1774889

Please sign in to comment.