DEV Community


Migrate to Actix-Web 2.0

werner profile image Werner Echezuría ・4 min read

I decided it was time for a version upgrade to Actix Web 2.0, so, in order to make it easier I migrated most of the endpoints to GraphQL.


I have to recognize I had my doubts about GraphQL, but after a few steps I took in this project, I recognize why it's growing in popularity. A few benefits:

  • I just need one endpoint and define through functions and methods what resource will be modified or added.
  • I don't need to define every detail, that's what GraphQL is for, the client will define what is the content that will be defined and what will be modified.
  • I don't need to define a standard, it's already established.

In order to make it work, I created one file for mutations and another for queries, like this:


use juniper::FieldResult;
use crate::models::Context;
use crate::models::sale::{Sale, NewSale, FullSale};
use crate::models::sale_product::NewSaleProducts;
use crate::models::price::NewPriceProductsToUpdate;
use crate::models::product::{FullProduct, Product, NewProduct};
use crate::models::sale_state::Event;
use crate::models::price::{NewPrice, Price};

pub struct Mutation;

    Context = Context,
impl Mutation {
    fn createSale(
        context: &Context,
        param_new_sale: NewSale,
        param_new_sale_products: NewSaleProducts,
    ) -> FieldResult<FullSale> {
        Sale::create_sale(context, param_new_sale, param_new_sale_products)

    fn approveSale(context: &Context, sale_id: i32) -> FieldResult<bool> {
        Sale::set_state(context, sale_id, Event::Approve)

    fn cancelSale(context: &Context, sale_id: i32) -> FieldResult<bool> {
        //TODO: perform credit note or debit note
        Sale::set_state(context, sale_id, Event::Cancel)

    fn paySale(context: &Context, sale_id: i32) -> FieldResult<bool> {
        //TODO: perform collection
        Sale::set_state(context, sale_id, Event::Pay)

    fn partiallyPaySale(context: &Context, sale_id: i32) -> FieldResult<bool> {
        //TODO: perform collection
        Sale::set_state(context, sale_id, Event::PartiallyPay)

    fn updateSale(
        context: &Context,
        param_sale: NewSale,
        param_sale_products: NewSaleProducts,
    ) -> FieldResult<FullSale> {
        Sale::update_sale(context, param_sale, param_sale_products)

    fn destroySale(context: &Context, sale_id: i32) -> FieldResult<bool> {
        Sale::destroy_sale(context, sale_id)

    fn createProduct(
        context: &Context,
        param_new_product: NewProduct,
        param_new_price_products: NewPriceProductsToUpdate,
    ) -> FieldResult<FullProduct> {
        Product::create_product(context, param_new_product, param_new_price_products)

    fn updateProduct(
        context: &Context,
        param_product: NewProduct,
        param_price_products: NewPriceProductsToUpdate,
    ) -> FieldResult<FullProduct> {
        Product::update_product(context, param_product, param_price_products)

    fn destroyProduct(context: &Context, product_id: i32) -> FieldResult<bool> {
        Product::destroy_product(context, product_id)

    fn createPrice(context: &Context, new_price: NewPrice) -> FieldResult<Price> {
        Price::create(context, new_price)

    fn updatePrice(context: &Context, edit_price: NewPrice) -> FieldResult<Price> {
        Price::update(context, edit_price)

    fn destroyPrice(context: &Context, price_id: i32) -> FieldResult<bool> {
        Price::destroy(context, price_id)


use juniper::FieldResult;
use crate::models::Context;
use crate::models::sale::{Sale, NewSale, ListSale, FullSale};
use crate::models::product::{Product, ListProduct, FullProduct};
use crate::models::price::{PriceList, Price};

pub struct Query;

    Context = Context,
impl Query {
    fn listSale(context: &Context, search: Option<NewSale>, limit: i32) -> FieldResult<ListSale> {
        Sale::list_sale(context, search, limit)

    fn sale(context: &Context, sale_id: i32) -> FieldResult<FullSale> {
        Sale::sale(context, sale_id)

    fn listProduct(context: &Context, search: String, limit: i32, rank: f64) -> FieldResult<ListProduct> {
        Product::list_product(context, search, limit, rank)

    fn product(context: &Context, product_id: i32) -> FieldResult<FullProduct> {
        Product::product(context, product_id)

    fn ListPrice(context: &Context) -> FieldResult<PriceList> {

    fn price(context: &Context, price_id: i32) -> FieldResult<Price> {
        Price::find(context, price_id)


And another file for the schema:


use crate::graphql::query::Query;
use crate::graphql::mutation::Mutation;

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

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

Actix Web 2.0

I think I like the new way to define endpoints in ActixWeb, it looks like Rocket, especially because of routes annotations. The big change was defined in src/graphql/


pub mod query;
pub mod mutation;
pub mod schema;

use std::sync::Arc;
use actix_web::{web, Error, HttpResponse, post, get};
use juniper::http::graphiql::graphiql_source;
use juniper::http::GraphQLRequest;
use schema::Schema;

use crate::models::create_context;
use crate::handlers::LoggedUser;
use crate::db_connection::PgPool;
use crate::serde::ser::Error as SerdeError;

pub async fn graphiql() -> HttpResponse {
    let html = graphiql_source("");
        .content_type("text/html; charset=utf-8")

pub async fn graphql(
    st: web::Data<Arc<Schema>>,
    data: web::Json<GraphQLRequest>,
    user: LoggedUser,
    pool: web::Data<PgPool>
) -> Result<HttpResponse, Error> {
    let user =
        web::block(move || {
            let pg_pool = pool
                .map_err(|e| {

            let ctx = create_context(, pg_pool);

            let res = data.execute(&st, &ctx);
            Ok::<_, serde_json::error::Error>(serde_json::to_string(&res)?)

There are other changes, more difficult that implies altering the integration tests, the more important one was the test server.


        let srv = RefCell::new(test_server(move || {
                                .allowed_methods(vec!["GET", "POST", "PUT", "PATCH", "DELETE"])
                    ,  |_| AppConfig::default(),

If you want to take a look at the source code, you can do it through this link

PS. I'm openly looking for a new job (remote), Experience in Ruby on Rails and Rust enthusiast, :).

Discussion (1)

Editor guide
sunsoftio profile image

Hi Werner,

do you want to rewrite the software with Rust and Actix?