3 Frameworks for Building APIs Using Rust

3 Frameworks for Building APIs Using Rust

Below, we'll review three superb frameworks for building APIs using Rust.

Originally published by  TYLER CHARBONEAU at nordicapis.com

Originally a personal project of Mozilla’s Graydon Hoare, Rust’s development began in 2006. It first said Hello World following its official announcement by Mozilla in 2010. Though Rust hasn’t enjoyed Python’s colossal growth, users love the language immensely. Rust topped Stack Overflow’s “most loved programming languages” ranking list in 20162017, and 2018. We only expect it to grow more popular — especially since it’s continually being refined.

We previously assessed the various pros and cons of Rust, and find it incredibly useful. The language shares many similarities with C and C++, while offering essential syntax modifications of its own. Accordingly, developers praise Rust for its open-source development and performance. This makes it a prime candidate for API creation. Below, we’ll review three superb frameworks for building APIs using Rust.

1. Actix-web

“rust’s powerful actor system and most fun web framework”

Built for speed and supportive of modern features, Actix-web is a framework focused on safe API creation. It was built to be usable and lightweight. Initially released in October of 2017 and maintained by Nikolay Kim, Actix-web supports the Actix actor framework. It has active Gitter and Reddit communities, and GitHub contributions are always welcomed. Actix-web is open source and has 108 contributors on GitHub. With over 2,500 commits and 89 releases, the framework is continually in development. Microsoft uses Actix-web in some applications, though additional details are murky.

Benefits

  • Supports both HTTP/1.x and HTTP 2.0 protocols
  • Like Rocket, it supports data streaming while adding pipelining support, cutting down on response times by packaging requests on a single TCP connection
  • Includes client support for asynchronous request handling, facilitating multiple concurrent executions without hiccups
  • WebSockets support on both the client and server sides
  • Developers can create customized request routes for easier resource access. This also allows for more consistent URI structuring
  • Actix-web can serve both static and dynamic assets via OpenSSL or rustls
  • One of the fastest-benchmarking Rust frameworks to date, with ample raw performance

Drawbacks

  • Heavy reliance on unsafe code instances to allow for improved performance, though this has been mitigated in recent versions
  • Actix-web can be more complicated than Rocket to get started with, for example
2. Rocket

“Rocket is a web framework for Rust that makes it simple to write fast, secure web applications”

Built using Rust’s core principles, Rocket is an excellent open-source framework for developers seeking a comprehensive toolset. Rocket includes a plethora of pre-packaged features, yet is quite extensible. Sergio Benitez publicly released Rocket in December of 2016, and remains a key contributor to date. Rocket doesn’t appear to have a bustling community via IRC, or stemming from its website. However, Rocket’s news blog helps developers keep abreast of ongoing changes. Accordingly, its development effort is strong. At the time of writing, 128 GitHub contributors have aided Rocket’s development since launch, and there have been over 1,250 commits. Having been updated 45 times following initial release, the current build of Rust is v0.4. On average, a new version of Rocket is released approximately every 18 days.

Benefits

  • As its namesake suggests, Rocket is built for speed with fast templating and incoming/outgoing data streaming, which is file-size agnostic
  • The request-response process occurs in only three steps, emphasizing sound validation, customized error reporting, and simple arbitrary-handler functions
  • Type-safe programming ensures requests follow successive, matching routes until validation does or doesn’t occur
  • Form support is rich, and troubleshooting is simple. Bad requests don’t call your functions, and simple type changes help nail down faulty programming
  • JSON support is baked in via Serialize or Deserialize. Using the FromData trait ensures easy value creation by type
  • Request guards block your handlers from running if predetermined conditions aren’t met. Customizable ApiKey guards prevent unneeded sensitive operations, enforcing both type and API-header validations

Drawbacks

  • Rocket has depended on nightly Rust and doesn’t run on stable versions of the Rust compiler. This feature is pending, however – v0.5 is in development, and will add support for Rust stable
  • Multipart form support is still a bit clunky, complicating file uploads and submissions. Again, improved support is slated for v0.5
  • It can be tricky keeping all components of Rocket properly updated over time
  • Ongoing changes in Rust nightly can cause unpredictable, breaking changes


3. Tower Web

Tower Web is a fast web framework that aims to remove boilerplate.

With a focus on simplifying API development via boilerplate removal, Tower Web is a lean-yet-powerful Rust framework. It’s built upon Tower, a complimentary library containing useful network components. Much like Actix-web leans on Actix, Tower Web does the same with Tower. It’s also built upon Tokio and Hyper – a non-blocking I/O platform and HTTP client, respectively. As a result, Tower Web is a well-rounded platform ready to tackle Rust API development. It’s also relatively new – Carl Lerche released Tower Web in August of 2018. He currently spearheads development efforts on GitHub, alongside 24 contributors. Since inception, it has been updated 11 times, approximately one release every 22 days. Its current version is v0.3.6.

Benefits

  • Since Tower Web is built upon Tower, Tokio, and Hyper, it has a solid foundation that offers promising future potential. As these components grow, Tower Web may reach feature parity with frameworks like Rocket
  • Complete asynchronicity allows multiple processes to run simultaneously, eliminating bottlenecks
  • HTTP is separated from your application’s logic, erasing the need for boilerplate and cutting down on cruft
  • Tower Web is compatible with Rust stable, meaning ongoing changes to nightly won’t break your build

Drawbacks

  • Since Tower Web is newer, developers are still waiting for additional components, such as middleware, to be written
  • Documentation could be more comprehensive
Comparing Use Cases

Actix-web’s big advantages lie in two areas: actor management and speed. If you’re building out a web application based around accounts (email, web services with SOAP endpoints), the framework truly shines. This actor support is available in other frameworks like Rocket, though Actix-web’s implementation is more elegant. API builds reliant on the WebSockets protocol will mesh harmoniously with Actix-web. If you’re trying to squeeze as much performance out of your web servers as possible, the protocol’s low overhead will facilitate that.

If you want to be on the bleeding edge and design web applications in compliance with emerging standards, Rocket is a great choice. That’s not saying the framework falters in other scenarios, but its current reliance on nightly Rust does raise stability concerns. Because Rocket isn’t asynchronous, it can only handle so many connections at any given time. Consequently, it may be better suited to smaller projects, or at least to web applications where active requests are minimized. However, Rocket is quite adept at handling large quantities of data. As the framework matures, it may very well power some large, high-profile projects. Rocket takes much of the guesswork out of the equation for newer users.

For developers looking to write the leanest code possible, Tower Web is a fantastic option. Boilerplate code has been placed on the chopping block, cutting down on production time. Code written within Tower Web has purpose, which can truly help with larger projects. It’s also straightforward to incorporate middleware into Tower Web – simplifying API creation and promoting a better experience for the end user. Fully asynchronous, the framework can also handle a massive amount of requests in parallel. Popular services built around numerous sessions will benefit significantly from this.

Originally published by  TYLER CHARBONEAU at nordicapis.com

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

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

Learn More

☞ 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


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

Hire PHP Developer and Web Developer for your Online Business

Hire PHP Developer and Web Developer for your Online Business

PHP is widely used open-source scripting language it helps in making dynamically easy your websites and web application. Mobiweb Technology is your best technical partner and offering you solution for any kind of website and application...

PHP is widely used open-source scripting language it helps in making dynamically easy your websites and web application. Mobiweb Technology is your best technical partner and offering you solution for any kind of website and application development. To hire PHP developer and web developer at affordable prices contact Mobiweb Technology via [email protected]

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