How to use Prisma with GraphQL?

How to use Prisma with GraphQL?

In this post is going to cover some basics about GraphQL, and then show how to use Prisma with your GraphQL instance.

Introduction

Traditional APIs focused on REST services with standard endpoints and HTTP verbs like GET, POST, PUT, and DELETE. With the advent of GraphQL, REST now has an alternative that is just as strong and viable for any project.

There is a lot of information available about working with GraphQL, but a common issue is connecting your GraphQL server to your database. Prisma is an excellent tool that greatly simplifies interacting with your database via a GraphQL API.

This post is going to cover some basics about GraphQL, and then show how to use Prisma with your GraphQL instance.

We will be working with a GraphQL project that interacts with a Star Wars character database. This project is available on Github here. I will be walking through this post on a Mac, but as long as you have the Bash Shell installed the commands should be the same.

Additionally, if you want to have a fun way to lookup Star Wars characters for your database, check out the Star Wars databank here.

Some background

GraphQL was originally developed by Facebook in 2012 and consists of a query and manipulation language for APIs. Instead of building traditional request/response models, GraphQL enables you to interact with your application’s data through specific queries. This can be actual queries, or you can update your data using mutations. This is particularly beneficial when you are concerned with efficiency in your APIs, and only retrieving what is necessary.

With GraphQL you build a data schema and resolvers to control what data is returned and how you interact with it. Connecting to the data via a database can get complicated, but that’s where Prisma shines.

Prisma provides a database client for your GraphQL API. This client makes your life as a developer significantly easier because it autogenerates the basic database commands for you.

The Prisma CLI will even spin up a database within a docker container with just a few commands. Prisma currently supports TypeScript, JavaScript, Flow, and Go.

In the next sections, we will be creating a GraphQL API that interacts with a Star Wars character database. We will first set up the GraphQL API, and then connect it to a database with Prisma.

GraphQL set up

To start, go to your terminal and let’s create a folder and initialize a project with the following:

mkdir starwars-graphql
cd starwars-graphql
yarn init -y

(NOTE: we’re using yarn here but you can just as easily use npm)

This just creates a basic package.json and then enables you to have a workspace to start creating your GraphQL server.

Now let’s create a source directory with the traditional index.js file for your server:

mkdir src
touch src/index.js

Next, install the graphql-yoga package to automate creating a full instance of GraphQL:

yarn add graphql-yoga

Now that we’ve got our dependencies, let’s add the following:

  • Type Definition = defines our data and operations
  • Resolvers = ways to handle the GraphQL queries of our data
  • Server = formal instantiation of our GraphQL instance

In your index.js file go ahead and add the following:

const { GraphQLServer } = require('graphql-yoga')

// Type Definition
const typeDefs = `
type Query {
  character: String!
}
`

// Resolvers
const resolvers = {
  Query: {
    character: () => `The force is strong with this API!`
  }
}

// Server
const server = new GraphQLServer({
  typeDefs,
  resolvers,
})
server.start(() => console.log(`Server is running on http://localhost:4000`))

Now let’s go ahead and test your GraphQL instance with the following:

node src/index.js

This should start up your server at http://localhost:4000. If you open your browser to that port you should see something like this:

What is this? This is the GraphQL Playground. The GraphQL Playground enables you to interact with your GraphQL server locally. In the panel on the left, you can put queries that will directly interact with your data. You can also open the schema on the right which shows the data this server is expecting (more on that in a second).

For the purposes of our initial test, go ahead and copy and paste the following into the left panel and click the little arrow to run your first query:

query {
  character
}

Once you’ve run your query you should see the following:

What did this do? It just ran your first GraphQL query!

Now let’s look at the code line by line:

// Type Definition
const typeDefs = 
`type Query {
  character: String!
}`;

The typeDefs set up how you are interacting with your data, and this is also known as the GraphQL Schema. In this case, the data is very simple, and only has a field called character that is a string. The exclamation mark (!) means the character field will not return a null value.

A GraphQL schema determines the behavior of your API and uses special root types. These root types are special values that GraphQL uses to determine how the API interacts with the data.

The three different root types commonly used are:

  • Query = selecting data
  • Mutation = changing data
  • Subscription = subscribing to a stream of data

NOTE: For our project, we are just going to be creating a query and a mutation. For more on subscription, please refer to the GraphQL blog post here.

// Resolvers
const resolvers = {
  Query: {
    character: () => `The force is strong with this API!`
  }
};

The resolver determines how the response is handled from your type definitions. In this case whenever a query is created for character the string “the force is strong with this API!” is returned.

How to set up our Star Wars GraphQL server

So for our Star Wars GraphQL server, we are going to be interacting with a database of characters with the following fields:

  • name
  • species
  • affiliation (Rebel Alliance or Empire)
  • weapon

Let’s first change our typeDefs section to reflect a character object with the following:

// Type Definition
const typeDefs = `
type Query {
  characters: [Character!]!
}

type Character {
  name: String!,
  species: String!,
  affiliation: affiliation!
  weapon: String!
}

enum affiliation {
  REBEL_ALLIANCE,
  EMPIRE
}`;

Note for more info on GraphQL types and fields, please check out the official documentation here.

So what did this do? Let’s go section by section.

type Query {
  characters: [Character!]!
}

First, we defined what our query will look like. Here, a query with the value “characters” will return an array of Character objects. The exclamation mark (!) indicates that the return type will not be null.

type Character {
  name: String!,
  species: String!,
  affiliation: affiliation! 
  weapon: String!
}

Next, we define our character object.

enum affiliation {
  REBEL_ALLIANCE,
  EMPIRE
}

Finally, we define an enum of possible affiliation values (in this case either the Rebel Alliance or Empire).

Now with the Query and Object definitions set up, let’s define resolvers to handle the actual query.

Replace the resolvers section from above with the following:

// local storage of characters, this will be moved later
const characters = [
  {
    name: "Han Solo",
    species: "Human",
    affiliation: "REBEL_ALLIANCE",
    weapon: "blaster rifle"
  },
  {
    name: "Chewbacca",
    species: "Wookie",
    affiliation: "REBEL_ALLIANCE",
    weapon: "bowcaster"
  }
];

// resolving queries
const resolvers = {
  Query: {
    characters: () => characters
  },
  Character: {
    name: parent => parent.name,
    species: parent => parent.species,
    affiliation: parent => parent.affiliation,
    weapon: parent => parent.weapon
  }
};

What did this do?

// local storage of characters, this will be moved later
const characters = [
  {
    name: "Han Solo",
    species: "Human",
    affiliation: "REBEL_ALLIANCE",
    weapon: "blaster rifle"
  },
  {
    name: "Chewbacca",
    species: "Wookie",
    affiliation: "REBEL_ALLIANCE",
    weapon: "bowcaster"
  }
];

First, we define a local storage version of our characters. We are going to be retrieving those directly from the database once we get Prisma set up. Here we are just defining them in a local array so we can build the basics of our server before connecting with Prisma.

// resolving queries
const resolvers = {
  Query: {
    characters: () => characters
  },
  Character: {
    name: parent => parent.name,
    species: parent => parent.species,
    affiliation: parent => parent.affiliation,
    weapon: parent => parent.weapon
  }
};

Next, we define a resolver for our query to select the characters. Why are we using parent here? This is so GraphQL can resolve the nested query.

Basically, we first had to define for the Query operation to return a characters array. Using the parent field here, we are saying that for every record from the characters array lets pull back the corresponding character fields.

Now go ahead and restart your server with node src/index.js .

Open up http://localhost:4200 to open the GraphQL Playground.

Copy the following query into the playground:

query {
  characters {
    name
    species
    affiliation
    weapon
  }
}

Run your query and you should see something like this:

Note: If you saw an error about null fields (or something to that effect), it just means that GraphQL was unable to return a value based on your query. You probably just have a syntax error, and I’d recommend just consulting the final GitHub project to make sure your code matches.

If you click on the SCHEMA button you will see the values that we’ve defined above. This is a great way to validate what GraphQL is looking for.

Changing the data with a mutation

So just to review, there are three different root fields that are commonly used:

  • Query = selecting data
  • Mutation = changing data
  • Subscription = subscribing to a stream of data

So far we have set up a Query that we can apply to an array in memory. Now let’s write a Mutation that will enable you to update the array of values and create a character.

Add the following to the typeDefs section:

type Mutation {
  post(name: String!, species: String!, affiliation: affiliation!, weapon: String!): Character!
}

This is saying that for a post with the associated arguments (name, species, affiliation, and weapon, create a Character object.

Next in the resolvers add a function to handle the Mutation operation with the following:

Mutation: {
  post: (parent, args) => {
    const character = {
      name: args.name,
      species: args.species,
      affiliation: args.affiliation,
      weapon: args.weapon
    };
    characters.push(character);
    return character;
  }
}

What this is saying is that for a mutation, create a character with the args provided and push it to the characters array.

If you go and run your server with node src/index.js , then open the GraphQL IDE in the browser at localhost:4000.

In the GraphQL IDE run the following command:

mutation {
  post(
    name: "Darth Vader"
    species: "Human",
    affiliation: EMPIRE,
    weapon: "lightsaber"
  ) {
    name
  }
}

Now go ahead and run the query we created before and you should see something like the following:

Then if you comment out the mutation and run the original Query, you should see the array updated with the value for “Darth Vader” here:

Congratulations! You now have a mutation setup for your GraphQL server!

Creating a Prisma client

What is Prisma? As mentioned in the intro, Prisma provides a client that GraphQL can use to interact with your database.

Why is this important? When you use GraphQL, you will want to connect your server to an actual database. Database access requires building direct SQL queries or using a traditional Object-Relational Mapping (ORM) to interact with the database. SQL Queries are error-prone as they have to be sent over as strings. ORMs typically are very simplistic and difficult to scale to properly work with complex applications.

…enter Prisma

Prisma takes care of the challenge of connecting your server to your database through (1) a generated client and (2) a server that translates your GraphQL calls into commands for your database.

The resulting flow should look like the following:

The generated client becomes part of your GraphQL server and serves as the means for translating your queries, mutations, and subscriptions into database calls.

Prisma has a CLI which makes this whole process very easy. For our project, we’re going to use the Prisma CLI to stand up a demo database that’s hosted with AWS Aurora DB.

So let’s get started!

First, let’s create a Prisma directory in the project:

mkdir prisma
touch prisma/prisma.yml
touch prisma/datamodel.prisma

Open the datamodel.prisma file and add the following:

type Character {
  id: ID! @id
  name: String!
  species: String!
  affiliation: affiliation!
  weapon: String!
}

enum affiliation {
  REBEL_ALLIANCE
  EMPIRE
}

This defines the Character object for Prisma. If you notice we created the @id value here, this is so that every record created with Prisma is unique. Prisma will automatically generate the ID value for us, with each new record.

Next, open the prisma.yml file and add the following:

# HTTP Endpoint
endpoint: ""

# location of the datamodel file
datamodel: datamodel.prisma

# identifiy the language and location of the Prisma Client
generate:
  - generator: javascript-client
    output: ../src/generated/prisma-client

This file does the following:

  • identifies the HTTP endpoint for your client (note this will be filled out by the CLI when we run the deploy command)
  • defines the location of the datamodel file
  • defines the application language
  • defines the location of the (generated) Prisma Client

Now we’re ready to actually build the client so let’s install the Prisma CLI globally with:

<pre>yarn global add prisma</pre>

Once that completes, we’ll need to generate the Prisma Client code. Any of the commands with the Prisma CLI are prefixed with prisma, and will result in you answering the prompts with information about database type, location, etc.

In your terminal, go to the project’s root and run the following:

prisma deploy

In your terminal, select “Demo server + MySQL database” to enable Prisma to build you a demo in the cloud (it’s free). You’re terminal should look similar to the following:

Now with the infrastructure deployed into the cloud, you can generate the Prisma Client which will be used by your GraphQL API by running the following (at the project’s root):
prisma generate.

Now the Prisma Client is setup, and your Prisma Server is deployed.

In order to work with our Prisma Client, we will need to install the prisma-client-lib package with: yarn add prisma-client-lib

Once you’ve got the client library installed, you can test creating a record on your database instance by running the following (saved in the sample project as prisma_client.js):

const { prisma } = require("./generated/prisma-client");

async function main() {
  // Create a new character
  const newCharacter = await prisma.createCharacter({
    name: "Luke Skywalker",
    species: "Human",
    affiliation: "REBEL_ALLIANCE",
    weapon: "lightsaber"
  });
  console.log(
    `Created new character: ${newCharacter.name} (ID: ${newCharacter.id})`
  );

  const allCharacters = await prisma.characters();
  console.log(allCharacters);
}

main().catch(e => console.error(e));

Once you’ve run this code, you can also view it in the Prisma Console if you go to https://app.prisma.io/.

Running the prisma_client.js should result in something like the following:

Then if you go to your browser at https://app.prisma.io/, you can look directly at the data we were just working with:

Congratulations on getting the client setup! 🎉

Connecting to the Prisma Client

So the last step is to connect our GraphQL server to our generated Client. The prisma_client.js file is a standalone file that runs to directly add values to the database. We want to use the query and mutation we created before to interact directly with our database.

First, open our server file at src/index.js and add a line to pull in the Prisma Client.
const { prisma } = require("./generated/prisma-client");

Then at the bottom of the server file let’s redefine our server instance with the following:

// Server
const server = new GraphQLServer({
  typeDefs,
  resolvers,
  context: { prisma }
});
server.start(() => console.log(`Server is running on http://localhost:4000`));

If you notice here we are defining a context object which is essentially our Prisma Client. We didn’t have this before because we were just running this in memory. Defining the context as our Prisma Client here enables all of the requests to use the same connection to our Prisma server and stood up the database. For more on context objects in GraphQL please consult the documentation here.

Finally, let’s change our resolvers in the src/index.js file to be the following:

// // local storage of characters, this will be moved later
// const characters = [
//   {
//     name: "Han Solo",
//     species: "Human",
//     affiliation: "REBEL_ALLIANCE",
//     weapon: "blaster rifle"
//   },
//   {
//     name: "Chewbacca",
//     species: "Wookie",
//     affiliation: "REBEL_ALLIANCE",
//     weapon: "bowcaster"
//   }
// ];

// resolving queries
const resolvers = {
  Query: {
    characters: (root, args, context, info) => {
      return context.prisma.characters();
    }
  },
  Mutation: {
    post: (root, args, context) => {
      return context.prisma.createCharacter({
        name: args.name,
        species: args.species,
        affiliation: args.affiliation,
        weapon: args.weapon
      });
    }
  }
};

What is this doing? This modified the query and mutation to point to the Prisma Client and server in lieu of our local array. Also, comment out our characters array as you see here since we won’t need them now.

So now that everything’s set up, lets go ahead and restart our server with node src/index.js and then open the GraphQL Playground at https://localhost:4000.

Run the following mutation to write a value into our database:

mutation {
  post(
    name: "Leia Organa"
    species: "Human",
    affiliation: REBEL_ALLIANCE,
    weapon: "blastor pistol"
  ) {
    name
  }
}

You should see the following:

Then run the Query to see that Leia is in our database:

NOTE: I had added some of the characters during testing, so you might have slightly different results here. The important thing is that Leia is on the list.

And you can pull up the Prisma Client site to see the record populated in your database as well:

You’ve now connected our GraphQL server to the database!

NOTE: At this point, you could also refactor the application. Typically larger applications have places for a resolvers file and a definitions file (instead of defining them all in one place). Here our API was really simple so I left the file as is.

Conclusion

So with this post, we learned some basics about GraphQL, and also how to use the powerful tool Prisma. We built a query and a mutation of a GraphQL server. We also learned about the GraphQL Playground. I hope this post has helped to get started with GraphQL and helped get your feet wet with development.

GraphQL is a really powerful technology that makes applications both efficient and easy to work with. Prisma is also a very useful tool that greatly simplifies building your GraphQL applications.

The next steps from this project would be to consider actually deploying your GraphQL server instance, and building a front-end client that could call your API.

Prisma also has a lot of features that we didn’t cover here. I highly recommend checking out their website and looking at their examples.

Mobile App Development Company India | Ecommerce Web Development Company India

Mobile App Development Company India | Ecommerce Web Development Company India

Best Mobile App Development Company India, WebClues Global is one of the leading web and mobile app development company. Our team offers complete IT solutions including Cross-Platform App Development, CMS & E-Commerce, and UI/UX Design.

We are custom eCommerce Development Company working with all types of industry verticals and providing them end-to-end solutions for their eCommerce store development.

Know more about Top E-Commerce Web Development Company

Practical Rust Web Development - GraphQL

Practical Rust Web Development - GraphQL

Practical Rust Web Development / GraphQL - According to the official homepage, GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data...

Originally published by Werner Echezuría  at dev.to

GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

One advantage of GraphQL is the flexibility it provides, in one query you can obtain everything you need allowing easy maintenance of the code over time and easier communication between the server and the client.

Juniper is a crate that allows the creation of a GraphQL server, we'll be using it in our project.

Let's continue with our online store, we're going to need to control our sales in the site, so, let's create a sales module that will receive a GraphQL query.

migrations/2019-07-28-191653_add_sales/up.sql:

CREATE TABLE sales (
  id SERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  sale_date DATE NOT NULL,
  total FLOAT NOT NULL
);

CREATE TABLE sale_products (
id SERIAL PRIMARY KEY,
product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE,
sale_id INTEGER NOT NULL REFERENCES sales(id) ON DELETE CASCADE,
amount FLOAT NOT NULL,
discount INTEGER NOT NULL,
tax INTEGER NOT NULL,
price INTEGER NOT NULL, --representing cents
total FLOAT NOT NULL
)

Then, we're gonna need the endpoint that will receive all our queries, this would be a post request.

src/graphql.rs:


pub fn graphql(
st: web::Data<Arc<Schema>>,
data: web::Json<GraphQLRequest>,
user: LoggedUser,
pool: web::Data<PgPool>
) -> impl Future<Item = HttpResponse, Error = Error> {
web::block(move || {
let pg_pool = pool
.get()
.map_err(|e| {
serde_json::Error::custom(e)
})?;

    let ctx = create_context(user.id, pg_pool);

    let res = data.execute(&amp;st, &amp;ctx);
    Ok::&lt;_, serde_json::error::Error&gt;(serde_json::to_string(&amp;res)?)
})
.map_err(Error::from)
.and_then(|user| {
    Ok(HttpResponse::Ok()
        .content_type("application/json")
        .body(user))
})

}

src/main.rs:

    HttpServer::new(
move || App::new()
.service(
web::resource("/graphql").route(web::post().to_async(graphql))
)

In order to output a response that reads the data we need a query, but if we need to modify state we'll be needing a mutation, let's add both resources in our sales module.

src/models/sale.rs:

use diesel::PgConnection;
use diesel::BelongingToDsl;
use diesel::sql_types;
use chrono::NaiveDate;
use juniper::{FieldResult};
use crate::schema;
use crate::schema::sales;
use crate::schema::sale_products;
use crate::db_connection::PgPooledConnection;
use crate::models::product::{ Product, PRODUCT_COLUMNS };
use crate::errors::MyStoreError;

#[derive(Identifiable, Queryable, Debug, Clone, PartialEq)]
#[table_name="sales"]
#[derive(juniper::GraphQLObject)]
#[graphql(description="Sale Bill")]
pub struct Sale {
pub id: i32,
pub user_id: i32,
pub sale_date: NaiveDate,
pub total: f64,
pub bill_number: Option<String>
}

#[derive(Insertable, Deserialize, Serialize, AsChangeset, Debug, Clone, PartialEq)]
#[table_name="sales"]
#[derive(juniper::GraphQLInputObject)]
#[graphql(description="Sale Bill")]
pub struct NewSale {
pub id: Option<i32>,
pub sale_date: Option<NaiveDate>,
pub user_id: Option<i32>,
pub total: Option<f64>,
pub bill_number: Option<String>
}

use crate::models::sale_product::{ SaleProduct, NewSaleProduct, NewSaleProducts, FullSaleProduct,FullNewSaleProduct };

#[derive(Debug, Clone)]
#[derive(juniper::GraphQLObject)]
pub struct FullSale {
pub sale: Sale,
pub sale_products: Vec<FullSaleProduct>
}

#[derive(Debug, Clone)]
#[derive(juniper::GraphQLObject)]
pub struct FullNewSale {
pub sale: NewSale,
pub sale_products: Vec<FullNewSaleProduct>
}

#[derive(Debug, Clone)]
#[derive(juniper::GraphQLObject)]
pub struct ListSale {
pub data: Vec<FullSale>
}

use std::sync::Arc;

pub struct Context {
pub user_id: i32,
pub conn: Arc<PgPooledConnection>,
}

impl juniper::Context for Context {}

pub struct Query;

type BoxedQuery<'a> =
diesel::query_builder::BoxedSelectStatement<'a, (sql_types::Integer,
sql_types::Integer,
sql_types::Date,
sql_types::Float8,
sql_types::Nullable<sql_types::Text>
),
schema::sales::table, diesel::pg::Pg>;

impl Sale {
fn searching_records<'a>(search: Option<NewSale>) -> BoxedQuery<'a> {
use diesel::QueryDsl;
use diesel::ExpressionMethods;
use crate::schema::sales::dsl::*;

    let mut query = schema::sales::table.into_boxed::&lt;diesel::pg::Pg&gt;();

    if let Some(sale) = search {
        if let Some(sale_sale_date) = sale.sale_date {
            query = query.filter(sale_date.eq(sale_sale_date));
        }
        if let Some(sale_bill_number) = sale.bill_number {
            query = query.filter(bill_number.eq(sale_bill_number));
        }
    }

    query
}

}

#[juniper::object(
Context = Context,
)]
impl Query {

fn listSale(context: &amp;Context, search: Option&lt;NewSale&gt;, limit: i32) 
    -&gt; FieldResult&lt;ListSale&gt; {
        use diesel::{ QueryDsl, RunQueryDsl, ExpressionMethods, GroupedBy };
        use crate::models::sale_product::SaleProduct;
        let conn: &amp;PgConnection = &amp;context.conn;
        let query = Sale::searching_records(search);

        let query_sales: Vec&lt;Sale&gt; =
            query
                .filter(sales::dsl::user_id.eq(context.user_id))
                .limit(limit.into())
                .load::&lt;Sale&gt;(conn)?;

        let query_products = 
            schema::products::table
                .inner_join(schema::sale_products::table)
                .select((PRODUCT_COLUMNS, 
                        (schema::sale_products::id, 
                         schema::sale_products::product_id, 
                         schema::sale_products::sale_id, 
                         schema::sale_products::amount,
                         schema::sale_products::discount,
                         schema::sale_products::tax,
                         schema::sale_products::price,
                         schema::sale_products::total)))
                .load::&lt;(Product, SaleProduct)&gt;(conn)?;

        let query_sale_products = 
            SaleProduct::belonging_to(&amp;query_sales)
                .inner_join(schema::products::table)
                .select(((schema::sale_products::id, 
                         schema::sale_products::product_id, 
                         schema::sale_products::sale_id, 
                         schema::sale_products::amount,
                         schema::sale_products::discount,
                         schema::sale_products::tax,
                         schema::sale_products::price,
                         schema::sale_products::total),
                         PRODUCT_COLUMNS))
                .load::&lt;(SaleProduct, Product)&gt;(conn)?
                .grouped_by(&amp;query_sales);

        let tuple_full_sale: Vec&lt;(Sale, Vec&lt;(SaleProduct, Product)&gt;)&gt; = 
            query_sales
                .into_iter()
                .zip(query_sale_products)
                .collect::&lt;Vec&lt;(Sale, Vec&lt;(SaleProduct, Product)&gt;)&gt;&gt;();

        let vec_full_sale = tuple_full_sale.iter().map (|tuple_sale| {
            let full_sale_product = tuple_sale.1.iter().map(|tuple_sale_product| {
                FullSaleProduct {
                    sale_product: tuple_sale_product.0.clone(),
                    product: tuple_sale_product.1.clone()
                }
            }).collect();
            FullSale {
                sale: tuple_sale.0.clone(),
                sale_products: full_sale_product
            }
        }).collect();

        Ok(ListSale { data: vec_full_sale })
    }

fn sale(context: &amp;Context, sale_id: i32) -&gt; FieldResult&lt;FullSale&gt; {
    use diesel::{ ExpressionMethods, QueryDsl, RunQueryDsl };

    let conn: &amp;PgConnection = &amp;context.conn;
    let sale: Sale =
        schema::sales::table
            .filter(sales::dsl::user_id.eq(context.user_id))
            .find(sale_id)
            .first::&lt;Sale&gt;(conn)?;

    let sale_products = 
        SaleProduct::belonging_to(&amp;sale)
            .inner_join(schema::products::table)
            .select(((schema::sale_products::id, 
                        schema::sale_products::product_id, 
                        schema::sale_products::sale_id, 
                        schema::sale_products::amount,
                        schema::sale_products::discount,
                        schema::sale_products::tax,
                        schema::sale_products::price,
                        schema::sale_products::total),
                        PRODUCT_COLUMNS))
            .load::&lt;(SaleProduct, Product)&gt;(conn)?
            .iter()
            .map(|tuple| {
                FullSaleProduct {
                    sale_product: tuple.0.clone(),
                    product: tuple.1.clone()
                }
            })
            .collect();
    Ok(FullSale{ sale, sale_products })
}

}

pub struct Mutation;

#[juniper::object(
Context = Context,
)]
impl Mutation {

fn createSale(context: &amp;Context, param_new_sale: NewSale, param_new_sale_products: NewSaleProducts) 
    -&gt; FieldResult&lt;FullSale&gt; {
        use diesel::{ RunQueryDsl, Connection, QueryDsl };

        let conn: &amp;PgConnection = &amp;context.conn;

        let new_sale = NewSale {
            user_id: Some(context.user_id),
            ..param_new_sale
        };

        conn.transaction(|| {
            let sale = 
                diesel::insert_into(schema::sales::table)
                    .values(new_sale)
                    .returning(
                        (
                            sales::dsl::id,
                            sales::dsl::user_id,
                            sales::dsl::sale_date,
                            sales::dsl::total,
                            sales::dsl::bill_number
                        )
                    )
                    .get_result::&lt;Sale&gt;(conn)?;

            let sale_products: Result&lt;Vec&lt;FullSaleProduct&gt;, _&gt; =
                param_new_sale_products.data.into_iter().map(|param_new_sale_product| {
                    let new_sale_product = NewSaleProduct {
                        sale_id: Some(sale.id),
                        ..param_new_sale_product.sale_product
                    };
                    let sale_product =
                        diesel::insert_into(schema::sale_products::table)
                            .values(new_sale_product)
                            .returning(
                                (
                                    sale_products::dsl::id,
                                    sale_products::dsl::product_id,
                                    sale_products::dsl::sale_id,
                                    sale_products::dsl::amount,
                                    sale_products::dsl::discount,
                                    sale_products::dsl::tax,
                                    sale_products::dsl::price,
                                    sale_products::dsl::total
                                )
                            )
                            .get_result::&lt;SaleProduct&gt;(conn);

                    if let Some(param_product_id) = param_new_sale_product.sale_product.product_id {
                        let product = 
                            schema::products::table
                                .select(PRODUCT_COLUMNS)
                                .find(param_product_id)
                                .first(conn);

                        Ok(
                            FullSaleProduct {
                                 sale_product: sale_product?, 
                                 product: product? 
                            }
                        )
                    } else {
                        Err(MyStoreError::PGConnectionError)
                    }
                }).collect();

            Ok(FullSale{ sale, sale_products: sale_products? })
        })
    }


fn updateSale(context: &amp;Context, param_sale: NewSale, param_sale_products: NewSaleProducts) 
    -&gt; FieldResult&lt;FullSale&gt; {
        use diesel::QueryDsl;
        use diesel::RunQueryDsl;
        use diesel::ExpressionMethods;
        use diesel::Connection;
        use crate::schema::sales::dsl;

        let conn: &amp;PgConnection = &amp;context.conn;
        let sale_id = param_sale.id.ok_or(
            diesel::result::Error::QueryBuilderError("missing id".into())
        )?;

        conn.transaction(|| {
            let sale = 
                diesel::update(dsl::sales
                                   .filter(dsl::user_id.eq(context.user_id))
                                   .find(sale_id))
                    .set(&amp;param_sale)
                    .get_result::&lt;Sale&gt;(conn)?;

            let sale_products: Result&lt;Vec&lt;FullSaleProduct&gt;, _&gt; =
                param_sale_products.data.into_iter().map (|param_sale_product| {
                    let sale_product =
                        diesel::update(schema::sale_products::table)
                            .set(&amp;param_sale_product.sale_product)
                            .get_result::&lt;SaleProduct&gt;(conn);

                    if let Some(param_product_id) = param_sale_product.sale_product.product_id {
                        let product = 
                            schema::products::table
                                .select(PRODUCT_COLUMNS)
                                .find(param_product_id)
                                .first(conn);

                        Ok(
                            FullSaleProduct {
                                 sale_product: sale_product?, 
                                 product: product? 
                            }
                        )
                    } else {
                        Err(MyStoreError::PGConnectionError)
                    }

                }).collect();

            Ok(FullSale{ sale, sale_products: sale_products? })
        })
    }

fn destroySale(context: &amp;Context, sale_id: i32) 
    -&gt; FieldResult&lt;i32&gt; {
        use diesel::QueryDsl;
        use diesel::RunQueryDsl;
        use diesel::ExpressionMethods;
        use crate::schema::sales::dsl;

        let conn: &amp;PgConnection = &amp;context.conn;
        diesel::delete(dsl::sales.filter(dsl::user_id.eq(context.user_id)).find(sale_id))
            .execute(conn)?;
        Ok(sale_id)
    }

}

pub type Schema = juniper::RootNode<'static, Query, Mutation>;

pub fn create_schema() -> Schema {
Schema::new(Query {}, Mutation {})
}

pub fn create_context(logged_user_id: i32, pg_pool: PgPooledConnection) -> Context {
Context { user_id: logged_user_id, conn: Arc::new(pg_pool)}
}

As you can see our business logic is very similar to a REST endpoint, what could change is the way we query the data, we then export the schema using create_schema function.

src/main.rs:

 let schema = std::sync::Arc::new(create_schema());

HttpServer::new(
move || App::new()
    .data(schema.clone())

Now, how can we fetch the data and performs our mutation?, we then need to write some tests and see if everything works as expected.

tests/sale_test.rs:

...
// create a sale:
let query =
format!(
r#"
{{
"query": "
mutation CreateSale($paramNewSale: NewSale!, $paramNewSaleProducts: NewSaleProducts!) {{
createSale(paramNewSale: $paramNewSale, paramNewSaleProducts: $paramNewSaleProducts) {{
sale {{
id
userId
saleDate
total
}}
saleProducts {{
product {{
name
}}
saleProduct {{
id
productId
amount
discount
tax
price
total
}}
}}
}}
}}
",
"variables": {{
"paramNewSale": {{
"saleDate": "{}",
"total": {}
}},
"paramNewSaleProducts": {{
"data":
[{{
"product": {{ }},
"saleProduct": {{
"amount": {},
"discount": {},
"price": {},
"productId": {},
"tax": {},
"total": {}
}}
}}]
}}
}}
}}"#,
...

// show a sale:

   let query = format!(r#"
        {{
            "query": "
                query ShowASale($saleId: Int!) {{
                    sale(saleId: $saleId) {{
                        sale {{
                            id
                            userId
                            saleDate
                            total
                        }}
                        saleProducts {{
                            product {{ name }}
                            saleProduct {{
                                id
                                productId
                                amount
                                discount
                                tax
                                price
                                total
                            }}
                        }}
                    }}
                }}
            ",
            "variables": {{
                "saleId": {}
            }}
        }}
    "#, id).replace("\n", "");

...

// update a sale

    let query = 
        format!(
        r#"
        {{
            "query": "
                mutation UpdateSale($paramSale: NewSale!, $paramSaleProducts: NewSaleProducts!) {{
                        updateSale(paramSale: $paramSale, paramSaleProducts: $paramSaleProducts) {{
                            sale {{
                                id
                                saleDate
                                total
                            }}
                            saleProducts {{
                                product {{ name }}
                                saleProduct {{
                                    id
                                    productId
                                    amount
                                    discount
                                    tax
                                    price
                                    total
                                }}
                            }}
                        }}
                }}
            ",
            "variables": {{
                "paramSale": {{
                    "id": {},
                    "saleDate": "{}",
                    "total": {}
                }},
                "paramSaleProducts": {{
                    "data":
                        [{{
                            "product": {{}},
                            "saleProduct": 
                            {{
                                "amount": {},
                                "discount": {},
                                "price": {},
                                "productId": {},
                                "tax": {},
                                "total": {}
                            }}
                        }}]
                }}
            }}
        }}"#

...

// delete a sale:

    let query = format!(r#"
        {{
            "query": "
                mutation DestroyASale($saleId: Int!) {{
                    destroySale(saleId: $saleId)
                }}
            ",
            "variables": {{
                "saleId": {}
            }}
        }}
    "#, id).replace("\n", "");

...

// search for a sale with specific date:

   let query = format!(r#"
        {{
            "query": "
                query ListSale($search: NewSale!, $limit: Int!) {{
                    listSale(search: $search, limit: $limit) {{
                        data {{
                            sale {{
                                id
                                saleDate
                                total
                            }}
                            saleProducts {{
                                product {{
                                    name
                                }}
                                saleProduct {{
                                    amount
                                    price
                                }}
                            }}
                        }}
                    }}
                }}
            ",
            "variables": {{
                "search": {{
                    "saleDate": "2019-11-10"
                }},
                "limit": 10
            }}
        }}
    "#).replace("\n", "");

You can take a look at the full source code here.

Originally published by Werner Echezuría  at dev.to

========================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ GraphQL with React: The Complete Developers Guide

☞ The Modern GraphQL Bootcamp (with Node.js and Apollo)

☞ Build a Realtime App with React Hooks and GraphQL

☞ Full-Stack React with GraphQL and Apollo Boost

☞ The Rust Programming Language

☞ Rust: Building Reusable Code with Rust from Scratch

☞  Programming in Rust: the good, the bad, the ugly.

☞  An introduction to Web Development with Rust for Node.js Developers

☞ Intro to Web Development with Rust for NodeJS Developers

☞ Introducing the Rust Crash Course