Michio JP

Michio JP

1565574982

Practical Rust Web Development - GraphQL

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(&st, &ctx);
        Ok::<_, serde_json::error::Error>(serde_json::to_string(&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::<diesel::pg::Pg>();

        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: &Context, search: Option<NewSale>, limit: i32) 
        -> FieldResult<ListSale> {
            use diesel::{ QueryDsl, RunQueryDsl, ExpressionMethods, GroupedBy };
            use crate::models::sale_product::SaleProduct;
            let conn: &PgConnection = &context.conn;
            let query = Sale::searching_records(search);

            let query_sales: Vec<Sale> =
                query
                    .filter(sales::dsl::user_id.eq(context.user_id))
                    .limit(limit.into())
                    .load::<Sale>(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::<(Product, SaleProduct)>(conn)?;

            let query_sale_products = 
                SaleProduct::belonging_to(&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::<(SaleProduct, Product)>(conn)?
                    .grouped_by(&query_sales);

            let tuple_full_sale: Vec<(Sale, Vec<(SaleProduct, Product)>)> = 
                query_sales
                    .into_iter()
                    .zip(query_sale_products)
                    .collect::<Vec<(Sale, Vec<(SaleProduct, Product)>)>>();

            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: &Context, sale_id: i32) -> FieldResult<FullSale> {
        use diesel::{ ExpressionMethods, QueryDsl, RunQueryDsl };

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

        let sale_products = 
            SaleProduct::belonging_to(&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::<(SaleProduct, Product)>(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: &Context, param_new_sale: NewSale, param_new_sale_products: NewSaleProducts) 
        -> FieldResult<FullSale> {
            use diesel::{ RunQueryDsl, Connection, QueryDsl };

            let conn: &PgConnection = &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::<Sale>(conn)?;

                let sale_products: Result<Vec<FullSaleProduct>, _> =
                    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::<SaleProduct>(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: &Context, param_sale: NewSale, param_sale_products: NewSaleProducts) 
        -> FieldResult<FullSale> {
            use diesel::QueryDsl;
            use diesel::RunQueryDsl;
            use diesel::ExpressionMethods;
            use diesel::Connection;
            use crate::schema::sales::dsl;

            let conn: &PgConnection = &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(&param_sale)
                        .get_result::<Sale>(conn)?;

                let sale_products: Result<Vec<FullSaleProduct>, _> =
                    param_sale_products.data.into_iter().map (|param_sale_product| {
                        let sale_product =
                            diesel::update(schema::sale_products::table)
                                .set(&param_sale_product.sale_product)
                                .get_result::<SaleProduct>(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: &Context, sale_id: i32) 
        -> FieldResult<i32> {
            use diesel::QueryDsl;
            use diesel::RunQueryDsl;
            use diesel::ExpressionMethods;
            use crate::schema::sales::dsl;

            let conn: &PgConnection = &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.

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

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

#rust #graphql #web-development

What is GEEK

Buddha Community

Practical Rust Web Development - GraphQL
Rahim Makhani

Rahim Makhani

1627274472

Develop An Unique Web App for your Firm

Web app represents the particular firm or organization for which it is developed. With the help of a web app, the firm owner can promote and increase their business by reaching more and more customers for their website or web app.

Every firm or organization must have its own web app to represent their company, what they do, what they provide users feedback, and a lot more. If you have your web app then users can know your company deeply and they can also show interest in your company.

To develop a unique web app contact Nevina Infotech that is the best web application development services provider company, that can help you to develop the web app for your firm as per your requirement.

#web application development company #web application development services #web app development company #custom web application development company #web app development services #web application development agency

Rahim Makhani

Rahim Makhani

1626238039

Find the best web app development company for your Startup

A web app is the best way to promote their business for startups. You can’t verbally go and tell everyone to visit your company, but your website or web app can do that. A web app can represent your company, and the visitors who are visiting your website or web app will get knowledge about your firm. Doing this can help you to increase your customer rate.

Nevina Infotech is the best web app development company to choose for developing your web app for your startup. We have a great team of web developers to work with. Our developers are dedicated and enthusiastic in their work.

#web application development company #web application development services #web app development company #custom web application development company #web app development services #custom web application development services

Top Responsive Web App Development Company in USA

AppClues Infotech is a well known web application development company. We help you to redefine your technological environment in this constantly changing digital age and competitive scenario. We specialize in developing customer centric web applications and provide you with enhanced web app development services as well as modern app marketing services which are focused to drive effectiveness and efficiency to your business.

We have team of creative website developers, designers, testers and quality engineers help businesses to operate their day-to-day activities actively. We serve superb app design & development services with great ease and awareness. With our responsive website design services, we provide user-friendly experience across all platforms (desktop, tablet, mobile).

Want A Beautiful Web App? We build 100% Responsive, Mobile Friendly, Professionally designed websites Loved By Clients & Google.

For more info:
Website: https://www.appcluesinfotech.com/
Email: info@appcluesinfotech.com
Call: +1-978-309-9910

#top responsive web app development company in usa #top web development companies in united states #best web development company usa #web application development company usa & india #web app development company in new york #top web app development company

Sunny Joshi

1626433760

Best Web Development Institute in Delhi - Online Course

If you want to pursue a career in Web Development, then you must opt for a Web Development course in Delhi. The Best Web Development Institute in Delhi is offered by Web Development colleges or institutions. They are designed to give you the necessary training to start a Web Development company or a web design business. These courses are generally provided by Techstack and other institutes in Delhi like APTRON, ADMEC, Next G Education etc. contact them and get the best online training in India. These institutes offer the basic courses with advanced level of courses.

Their courses help you learn Web Development and web marketing. The Best Web Development Institute in Delhi will enable you to understand the basic requirements of web design and Web Development. You can also enhance your web design skills through these Web Development courses in Delhi. The Web Development training in Delhi from an experienced Web Development institute in India will teach you Web Development principles and methods through their live projects. This course in Delhi from an institute of good repute has a host of practical, theoretical and educational lessons which can be followed closely. These Web Development training institutes in Delhi can be followed up with classroom learning.

Best Web Development Institute in Delhi:

The institute, which you select for training in Delhi can decide the schedule of classes. Some Web Designing institutes provide you with the opportunity of choosing a particular institute for online training also. In this case, you would be able to find Web Development courses in Delhi conducted by that institute. The course work of these institutes is generally taught in a classroom setting. Most of these institutes conduct both classroom sessions and web-based courses.

You can take Web Development training in Delhi from an institute which offers you real world-class online training. The Techstack Academy conducts its Web Development training in a traditional manner. Students can create their own applications using the modern lab environment provided at the best institute. Many institutes conduct their web-based online course in a modern lab setup. They have a number of modern labs where students can interact with their teachers and fellow students of the same institute. This interaction helps the students identify and grasp concepts in an easy manner.

In the Web Development training in Delhi from an institute of good repute, students can choose between Web Development with live projects, web design and Web Development with prototypes with manual coding. The students can choose the best course for them depending on their goals and preferences.

Also Read: Web Development Course in Delhi (#1 Best Training Institute)

With regard to Web Development training in Delhi, the first step that the students have to take is to register themselves at the Best Web Development Institute in Delhi of their choice. Once they are registered, they can choose the course of their choice. Some of these training centers offer intensive training while some do not offer any specialised training and hence students can easily find an online training institute in Delhi NCR also that suits their needs.

In order to get a web designing course in Delhi, one has to follow certain rules. First of all, the students need to contact the Web Development training centre in Delhi and register themselves for the course. The Best Web Development Institute in Delhi may conduct an interview process or conduct a web-course interview. The institute needs to understand the skills that the student possesses. Students can look forward to a bright career in web designing courses in Delhi as they can secure a number of jobs after completing their courses.

#web designing course in delhi #web development institute in delhi #web development training in delhi #web development course #web development institute #best web development institute in delhi

Rahim Makhani

Rahim Makhani

1626154510

Get the best web app that can fulfil your business needs

A web app is a type of app that can help you to represent your business. A web app is everything you need for your company’s growth because you can promote your business with its help. With its use, you can show the customers what products you have and come to know about your firm.

You can hire Nevina Infotech to develop your web app for your business. We are the most famous company for developing web apps because we also provide web application development services. We have a great team to work with to build your web app.

#web application development company #web application development services #web app development company #custom web application development company #web app development services #web application development agency