当前位置: 首页 > 工具软件 > pg_graphql > 使用案例 >

vscode构建rust_如何使用Rust构建功能强大的GraphQL服务器

郎正平
2023-12-01

vscode构建rust

Setting up a GraphQL server with Rust, Juniper, Diesel, and Actix; learning about Rust's web frameworks and powerful macros along the way.

使用Rust,Juniper,Diesel和Actix设置GraphQL服务器; 一路了解Rust的网络框架和强大的宏。

Source Code: github.com/iwilsonq/rust-graphql-example

源代码: github.com/iwilsonq/rust-graphql-example

Serving applications via GraphQL is quickly becoming the easiest and most effective way to deliver data to clients. Whether you're on a mobile device or a browser, it provides the data requested and nothing more.

通过GraphQL为应用程序提供服务正Swift成为向客户端传递数据的最简单,最有效的方法。 无论您是使用移动设备还是浏览器,它都可以提供所请求的数据,仅此而已。

Client applications no longer need to stitch together information from separate data sources. GraphQL servers are in charge of the integration, eliminating the need for excess data and round-trip requests for data.

客户端应用程序不再需要将来自单独数据源的信息拼接在一起。 GraphQL服务器负责集成,从而消除了对多余数据和数据往返请求的需求。

Of course, this implies that the server has to handle aggregating data from different sources, such as home-owned backend services, databases, or third party APIs. This may be resource intensive, how can we optimize for CPU time?

当然,这意味着服务器必须处理来自不同来源(例如,家庭后端服务,数据库或第三方API)的聚合数据。 这可能会占用大量资源,我们如何优化CPU时间?

Rust is a marvel of a language, pairing the raw performance of a low level language like C with the expressiveness of modern languages. It emphasizes type and memory safety, especially when there are potentially data races in concurrent operations.

Rust是一种语言的奇妙之处,它将诸如C之类的底层语言的原始性能与现代语言的表现力相结合。 它强调类型和内存安全性,尤其是在并发操作中可能存在数据争用时。

Let us see what goes into building a GraphQL server with Rust. We are going to learn about

让我们看看使用Rust构建GraphQL服务器的过程。 我们将学习

  • Juniper GraphQL Server

    Juniper GraphQL服务器
  • Actix web framework integrated with Juniper

    与瞻博网络集成的Actix Web框架
  • Diesel for quering a SQL database

    用于查询SQL数据库的柴油
  • Useful Rust macros and derived traits for working with these libraries

    用于这些库的有用的Rust宏和派生特征

Note that I will not go into detail regarding installing Rust or Cargo. This article assumes some preliminary knowledge of the Rust toolchain.

请注意,我不会详细介绍如何安装Rust或Cargo。 本文假设您对Rust工具链有一些初步的了解。

设置HTTP服务器 (Setting up an HTTP Server)

To begin, we need to initialize our project  with cargo and then install dependencies.

首先,我们需要使用cargo初始化我们的项目,然后安装依赖项。

cargo new rust-graphql-example
    cd rust-graphql-example

The initialization command bootstraps our Cargo.toml file which contains our projects dependencies as well as a main.rs file which has a simple "Hello World" example.

初始化命令引导我们的Cargo.toml文件,其中包含我们的项目依赖项;以及main.rs文件,其中包含一个简单的“ Hello World”示例。

// main.rs
    
    fn main() {
      println!("Hello, world!");
    }

As a sanity check, feel free to run cargo run in order to execute the program.

作为健全性检查,请随意运行cargo run以执行程序。

Installing the necessary libraries in Rust means adding a line containing the library name and version number. Let's update the dependencies sections of Cargo.toml like so:

在Rust中安装必要的库意味着添加包含库名称和版本号的行。 让我们像这样更新Cargo.toml的依赖项部分:

# Cargo.toml
    
    [dependencies]
    actix-web = "1.0.0"
    diesel = { version = "1.0.0", features = ["postgres"] }
    dotenv = "0.9.0"
    env_logger = "0.6"
    futures = "0.1"
    juniper = "0.13.1"
    serde = "1.0"
    serde_derive = "1.0"
    serde_json = "1.0"

This article will cover implementing a GraphQL server using Juniper as the GraphQL  library and Actix as the underlying HTTP server. Actix has a very nice API and works well with the stable version of Rust.

本文将介绍使用Juniper作为GraphQL库和Actix作为基础HTTP服务器来实现GraphQL服务器。 Actix有一个非常好的API,并且可以与Rust的稳定版本一起很好地工作。

When those lines are added, the next time the project compiles it will include those libraries. Before we compile, lets update main.rs with a basic HTTP server, handling the index route.

添加这些行后,下次项目编译时将包括这些库。 在编译之前,让我们使用基本的HTTP服务器更新main.rs,以处理索引路由。

// main.rs
    use std::io;
    
    use actix_web::{web, App, HttpResponse, HttpServer, Responder};
    
    fn main() -> io::Result<()> {
        HttpServer::new(|| {
            App::new()
                .route("/", web::get().to(index))
        })
        .bind("localhost:8080")?
        .run()
    }
    
    fn index() -> impl Responder {
        HttpResponse::Ok().body("Hello world!")
    }

The first two lines at the top bring the module we need into scope. The main function here returns an io::Result type, which allows us to use the question mark as a shorthand for handling results.

顶部的前两行将我们需要的模块纳入范围。 这里的主要函数返回一个io::Result类型,该类型允许我们使用问号作为处理结果的简写。

The server's routing and configuration is created in the instance of App, which is created in a closure provided by the HTTP server's constructor.

服务器的路由和配置在App实例中创建,该实例在HTTP服务器的构造函数提供的闭包中创建。

The route itself is handled by the index function, whose name is arbitrary. As long as this function properly implements Responder it can be used as the parameter for the GET request at the index route.

路由本身由索引函数处理,该索引函数的名称是任意的。 只要此功能正确实现了Responder它就可以用作索引路由上GET请求的参数。

If we were writing a REST API, we could proceed with adding more routes and associated handlers. We will see soon that we are trading a listing of route handlers for objects and their relations.

如果要编写REST API,则可以继续添加更多路由和关联的处理程序。 我们很快就会看到,我们正在交换对象及其关系的路由处理程序列表。

Now we will introduce the GraphQL library.

现在,我们将介绍GraphQL库。

创建GraphQL模式 (Creating the GraphQL Schema)

At the root of every GraphQL schema is a root query. From this root we can query lists of objects, specific objects,  and whatever fields they might contain.

每个GraphQL模式的根都是根查询。 从这个根目录,我们可以查询对象,特定对象以及它们可能包含的任何字段的列表。

Call this the QueryRoot, and we shall denote it with the same name in our code. Since we are not going to be setting up a database or any third party APIs, we'll be hard-coding the little data we have here.

将此称为QueryRoot,我们将在代码中以相同的名称表示它。 由于我们将不会建立数据库或任何第三方API,因此我们将对此处的少量数据进行硬编码。

To add a little color to this example, the schema will depict a generic list of members.

为在此示例中添加一点颜色,该架构将描述成员的通用列表。

Under src, add a new file called graphql_schema.rs along with the following contents:

在src下,添加一个名为graphql_schema.rs的新文件以及以下内容:

// graphql_schema.rs
    use juniper::{EmptyMutation, RootNode};
    
    struct Member {
      id: i32,
      name: String,
    }
    
    #[juniper::object(description = "A member of a team")]
    impl Member {
      pub fn id(&self) -> i32 {
        self.id  
      }
    
      pub fn name(&self) -> &str {
        self.name.as_str()
      }
    }
    
    pub struct QueryRoot;
    
    #[juniper::object]
    impl QueryRoot {
      fn members() -> Vec<Member> {
        vec![
          Member {
            id: 1,
            name: "Link".to_owned(),
          },
          Member {
            id: 2,
            name: "Mario".to_owned(),
          }
        ]
      }
    }

Along with our imports, we define our first GraphQL object in this project, the member. They are simple beings, with an id and name. We'll think about more complicated fields and patterns later.

除了导入,我们还定义了该项目中的第一个GraphQL对象,即成员。 它们是简单的生物,带有ID和名称。 稍后我们将考虑更复杂的字段和模式。

After stubbing out the QueryRoot type as a unit struct, we get to define the field itself. Juniper exposes a Rust macro called object which allows us to define fields on the different nodes throughout our schema. For now, we only have the QueryRoot node, so we'll expose a field called members on it.

在将QueryRoot类型作为单元结构存根之后,我们可以定义字段本身。 Juniper公开了一个名为object的Rust宏,该宏允许我们在整个架构的不同节点上定义字段。 目前,我们只有QueryRoot节点,因此我们将在其上公开一个名为member的字段。

Rust macros often have an unusual syntax compared to standard functions. They don't merely take some arguments and produce a result, they expand into much more complex code at compile time.

与标准函数相比,Rust宏通常具有不寻常的语法。 它们不仅接受一些参数并产生结果,而且在编译时扩展为复杂得多的代码。

公开架构 (Exposing the Schema)

Below our macro call to create the members field, we will define the RootNode type that we expose on our schema.

在创建成员字段的宏调用下面,我们将定义在架构上公开的RootNode类型。

// graphql_schema.rs
    
    pub type Schema = RootNode<'static, QueryRoot, EmptyMutation<()>>;
    
    pub fn create_schema() -> Schema {
      Schema::new(QueryRoot {}, EmptyMutation::new())
    }

Because of the strong typing in Rust, we are forced to provide the mutation object argument. Juniper exposes an EmptyMutation struct for just this occasion, that is, when we want to create a read-only schema.

由于Rust中的强类型输入,我们不得不提供突变对象参数。 Juniper仅在这种情况下(即,当我们要创建只读模式时)公开EmptyMutation结构。

Now that the schema is prepared, we can update our server in main.rs to handle the "/graphql" route. Since having a playground is also nice, we'll add a route for GraphiQL, the interactive GraphQL playground.

现在已经准备好架构,我们可以在main.rs中更新服务器以处理“ / graphql”路由。 由于拥有一个游乐场也很不错,我们将为交互式GraphQL游乐场GraphiQL添加一条路线。

// main.rs
    #[macro_use]
    extern crate juniper;
    
    use std::io;
    use std::sync::Arc;
    
    use actix_web::{web, App, Error, HttpResponse, HttpServer};
    use futures::future::Future;
    use juniper::http::graphiql::graphiql_source;
    use juniper::http::GraphQLRequest;
    
    mod graphql_schema;
    
    use crate::schema::{create_schema, Schema};
    
    fn main() -> io::Result<()> {
        let schema = std::sync::Arc::new(create_schema());
        HttpServer::new(move || {
            App::new()
                .data(schema.clone())
                .service(web::resource("/graphql").route(web::post().to_async(graphql)))
                .service(web::resource("/graphiql").route(web::get().to(graphiql)))
        })
        .bind("localhost:8080")?
        .run()
    }

You'll notice I've specified a number of imports that we will be using, including the schema we've just created. Also see that:

您会注意到,我指定了许多将要使用的导入,包括我们刚刚创建的架构。 另请参阅:

  • we call create_schema inside an Arc (atomically reference counted), to allow shared immutable state across threads (cooking with  here I know)

    我们在圆弧内调用create_schema (按原子计数),以允许线程之间共享不可变状态(在这里我知道用ok做饭)

  • we mark the closure inside HttpServer::new with move, indicating that the closure takes ownership of the inner variables, that is, it gains a copy of schema

    我们用move标记HttpServer :: new内部的闭包,表明该闭包拥有内部变量的所有权,也就是说,它获得了schema的副本

  • schema is passed to the data method indicating that it is to be used inside the application as shared state between the two services

    schema被传递给data方法,指示将在应用程序内部将其用作两个服务之间的共享状态

We must now implement the handlers for those two services. Starting with the "/graphql" route:

现在,我们必须为这两个服务实现处理程序。 从“ / graphql”路由开始:

// main.rs
    
    // fn main() ...
    
    fn graphql(
        st: web::Data<Arc<Schema>>,
        data: web::Json<GraphQLRequest>,
    ) -> impl Future<Item = HttpResponse, Error = Error> {
        web::block(move || {
            let res = data.execute(&st, &());
            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))
        })
    }

Our implementation of the "/graphql" route takes executes a GraphQL request against our schema from application state. It does this by creating a future from web::block and chaining handlers for success and error states.

我们对“ / graphql”路由的实现从应用程序状态开始针对我们的模式执行GraphQL请求。 它通过从web::block和链接处理程序创建成功和错误状态的未来来实现。

Futures are analogous to Promises in JavaScript, which is enough to understand this code snippet. For a greater explanation of Futures in Rust, I recommend this article by Joe Jackson.

期货类似于JavaScript中的Promises,足以理解此代码段。 要进一步了解Rust中的期货,我推荐Joe Jackson的这篇文章

In order to test out our GraphQL schema, we'll also add a handler for "/graphiql".

为了测试我们的GraphQL模式,我们还将为“ / graphiql”添加一个处理程序。

// main.rs
    
    // fn graphql() ...
    
    fn graphiql() -> HttpResponse {
        let html = graphiql_source("http://localhost:8080/graphql");
        HttpResponse::Ok()
            .content_type("text/html; charset=utf-8")
            .body(html)
    }

This handler is much simpler, it merely returns the html of the GraphiQL interactive playground. We only need to specify which path is serving our GraphQL schema, which is "/graphql" in this case.

这个处理程序要简单得多,它只返回GraphiQL交互式游乐场的html。 我们只需要指定服务于GraphQL模式的路径,在这种情况下为“ / graphql”。

With cargo run and navigation to http://localhost:8080/graphiql, we can try out the field we configured.

通过cargo run和导航到http:// localhost:8080 / graphiql ,我们可以尝试配置的字段。

It does seem to take a little more effort than setting up a GraphQL server with Node.js and Apollo but the static typing of Rust combined with its incredible performance makes it a worthy trade — if you're willing to work at it.

与使用Node.js和Apollo设置GraphQL服务器相比,它似乎需要花费更多的精力但是Rust的静态类型加上其令人难以置信的性能使其成为值得的交易-如果您愿意的话。

为真实数据设置Postgres (Setting up Postgres for Real Data)

If I stopped here, I wouldn't even be doing the examples in the docs much justice. A static list of two members that I wrote myself at dev time will not fly in this publication.

如果我在这里停留,我什至不会做文档中的例子太过公平。 在开发时写给自己的两个成员的静态列表不会在此出版物中发表。

Installing Postgres and setting up your own database belongs in a different article, but I'll walk through how to install diesel, the popular Rust library for handling SQL databases.

安装Postgres并设置自己的数据库属于另一篇文章,但是我将逐步介绍如何安装柴油 (流行的用于处理SQL数据库的Rust库)。

See here to install Postgres locally on your machine. You can also use a different database like MySQL in case you are more familiar with it.

请参阅此处在您的计算机上本地安装Postgres 。 如果您更熟悉数据库,也可以使用其他数据库,例如MySQL。

The diesel CLI will walk us through initializing our tables. Let's install it:

柴油CLI将引导我们完成初始化表的过程。 让我们安装它:

cargo install diesel_cli --no-default-features --features postgres

After that, we will add a connection URL to a .env file in our working directory:

之后,我们将连接URL添加到工作目录中的.env文件:

echo DATABASE_URL=postgres://localhost/rust_graphql_example > .env

Once that's there, you can run:

在那里,您可以运行:

diesel setup
    
    # followed by
    
    diesel migration generate create_members

Now you'll have a migrations folder in your directory. Within it, you'll have two SQL files: one up.sql for setting up your database, the other down.sql for tearing it down.

现在,您的目录中将包含一个migrations文件夹。 在其中,您将有两个SQL文件:一个用于建立数据库的up.sql,另一个用于拆除数据库的down.sql。

I will add the following to up.sql:

我将以下内容添加到up.sql中:

CREATE TABLE teams (
      id SERIAL PRIMARY KEY,
      name VARCHAR NOT NULL
    );
    
    CREATE TABLE members (
      id SERIAL PRIMARY KEY,
      name VARCHAR NOT NULL,
      knockouts INT NOT NULL DEFAULT 0,
      team_id INT NOT NULL,
      FOREIGN KEY (team_id) REFERENCES teams(id)
    );
    
    INSERT INTO teams(id, name) VALUES (1, 'Heroes');
    INSERT INTO members(name, knockouts, team_id) VALUES ('Link', 14, 1);
    INSERT INTO members(name, knockouts, team_id) VALUES ('Mario', 11, 1);
    INSERT INTO members(name, knockouts, team_id) VALUES ('Kirby', 8, 1);
    
    INSERT INTO teams(id, name) VALUES (2, 'Villains');
    INSERT INTO members(name, knockouts, team_id) VALUES ('Ganondorf', 8, 2);
    INSERT INTO members(name, knockouts, team_id) VALUES ('Bowser', 11, 2);
    INSERT INTO members(name, knockouts, team_id) VALUES ('Mewtwo', 12, 2);

And into down.sql I will add:

并添加到down.sql中:

DROP TABLE members;
    DROP TABLE teams;

If you've written SQL in the past, these statements will make some sense. We are creating two tables, one to store teams and one to store members of those teams.

如果您过去曾经编写过SQL,那么这些语句将很有意义。 我们正在创建两个表,一个表用于存储团队,一个表用于存储这些团队的成员。

I am modeling this data based on Smash Bros if you have not yet noticed. It helps the learning stick.

如果您尚未注意到,我将根据Smash Bros对该数据进行建模。 它有助于学习。

Now to run the migrations:

现在运行迁移:

diesel migration run

If you'd like to verify that the down.sql script works to destroy those tables, run: diesel migration redo.

如果您想验证down.sql脚本是否可以销毁那些表,请运行: diesel migration redo

Now the reason why I named the GraphQL schema file graphql_schema.rs instead of schema.rs, is because diesel overwrites that file in our src direction by default.

现在,之所以我将GraphQL模式文件命名为graphql_schema.rs而不是schema.rs,是因为默认情况下,柴油会沿src方向覆盖该文件。

It keeps a Rust macro representation of our SQL tables in that file. It is not so important to know how exactly this table! macro works, but try not to edit this file — the ordering of the fields matters!

它在该文件中保留了我们SQL表的Rust宏表示形式。 知道此table!精确程度不是很重要table! 宏有效,但请尽量不要编辑此文件-字段的顺序很重要!

// schema.rs (Generated by diesel cli)
    
    table! {
        members (id) {
            id -> Int4,
            name -> Varchar,
            knockouts -> Int4,
            team_id -> Int4,
        }
    }
    
    table! {
        teams (id) {
            id -> Int4,
            name -> Varchar,
        }
    }
    
    joinable!(members -> teams (team_id));
    
    allow_tables_to_appear_in_same_query!(
        members,
        teams,
    );

用柴油机连接处理机 (Wiring up our Handlers with Diesel)

In order to serve the data in our tables, we must first update our Member struct with the new fields:

为了提供表中的数据,我们必须首先使用新字段更新Member结构:

// graphql_schema.rs
    
    + #[derive(Queryable)]
    pub struct Member {
      pub id: i32,
      pub name: String,
    + pub knockouts: i32,
    + pub team_id: i32,
    }
    
    #[juniper::object(description = "A member of a team")]
    impl Member {
      pub fn id(&self) -> i32 {
        self.id  
      }
    
      pub fn name(&self) -> &str {
        self.name.as_str()
      }
    
    + pub fn knockouts(&self) -> i32 {
    +   self.knockouts
    + }
    
    + pub fn team_id(&self) -> i32 {
    +   self.team_id
    + }
    }

Note that we are also adding the Queryable derived attribute to Member. This tells Diesel everything it needs to know in order to query the right table in Postgres.

请注意,我们还将Queryable派生属性添加到Member 。 这将告诉Diesel在Postgres中查询正确的表所需了解的所有信息。

Additionally, add a Team struct:

此外,添加Team结构:

// graphql_schema.rs
    
    #[derive(Queryable)]
    pub struct Team {
      pub id: i32,
      pub name: String,
    }
    
    #[juniper::object(description = "A team of members")]
    impl Team {
      pub fn id(&self) -> i32 {
        self.id
      }
    
      pub fn name(&self) -> &str {
        self.name.as_str()
      }
    
      pub fn members(&self) -> Vec<Member> {
        vec![]
      }
    }

In a short while, we will update the members function on Team to return a database query. But first, let us add a root call for members.

不久后,我们将更新Teammembers函数以返回数据库查询。 但是首先,让我们为成员添加一个根调用。

// graphql_schema.rs
    + extern crate dotenv;
    
    + use std::env;
    
    + use diesel::pg::PgConnection;
    + use diesel::prelude::*;
    + use dotenv::dotenv;
    use juniper::{EmptyMutation, RootNode};
    
    + use crate::schema::members;
    
    pub struct QueryRoot;
    
    +  fn establish_connection() -> PgConnection {
    +    dotenv().ok();
    +    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    +    PgConnection::establish(&database_url).expect(&format!("Error connecting to {}", database_url))
    +  }
    
    #[juniper::object]
    impl QueryRoot {
      fn members() -> Vec<Member> {
    -   vec![
    -     Member {
    -       id: 1,
    -       name: "Link".to_owned(),
    -     },
    -     Member {
    -       id: 2,
    -       name: "Mario".to_owned(),
    -     }
    -   ]
    +   use crate::schema::members::dsl::*;
    +   let connection = establish_connection();
    +   members
    +     .limit(100)
    +     .load::<Member>(&connection)
    +     .expect("Error loading members")
      }
    }

Very good, we have our first usage of a diesel query. After initializing a connection, we use the members dsl, which is generated from our table! macros in schema.rs, and call load, indicating that we wish to load Member objects.

很好,我们第一次使用了柴油查询。 初始化连接后,我们使用从table!生成的成员dsl table! schema.rs中的宏,并调用load ,表明我们希望加载Member对象。

Establishing a connection means connecting to our local Postgres database by using the env variable we declared earlier.

建立连接意味着使用我们之前声明的env变量连接到本地Postgres数据库。

Assuming that was all input correctly, restart the server with cargo run, open GraphiQL and issue the members query, perhaps adding the two new fields.

假设所有输入均正确,请使用cargo run重新启动服务器,打开GraphiQL并发出成员查询,也许添加两个新字段。

The teams query will be very similar — the difference being we must also add a part of the query to the members function on the Team struct in order to resolve the relationship between GraphQL types.

team查询将非常相似-区别在于我们还必须将查询的一部分添加到Team结构上的member函数,以解决GraphQL类型之间的关系。

// graphql_schema.rs
    
    #[juniper::object]
    impl QueryRoot {
      fn members() -> Vec<Member> {
        use crate::schema::members::dsl::*;
        let connection = establish_connection();
        members
          .limit(100)
          .load::<Member>(&connection)
          .expect("Error loading members")
      }
    
    +  fn teams() -> Vec<Team> {
    +    use crate::schema::teams::dsl::*;
    +    let connection = establish_connection();
    +    teams
    +      .limit(10)
    +      .load::<Team>(&connection)
    +      .expect("Error loading teams")
    +  }
    }
    
    // ...
    
    #[juniper::object(description = "A team of members")]
    impl Team {
      pub fn id(&self) -> i32 {
        self.id
      }
    
      pub fn name(&self) -> &str {
        self.name.as_str()
      }
    
      pub fn members(&self) -> Vec<Member> {
    -    vec![]
    +    use crate::schema::members::dsl::*;
    +    let connection = establish_connection();
    +    members
    +      .filter(team_id.eq(self.id))
    +      .limit(100)
    +      .load::<Member>(&connection)
    +      .expect("Error loading members")
      }
    }

When running this is GraphiQL, we get:

运行GraphiQL时,我们得到:

I really like the way this is turning out, but there is one more thing we must add in order to call this tutorial complete.

我真的很喜欢这种方式的实现,但是为了使本教程完整,我们还必须添加一件事。

创建成员突变 (The Create Member Mutation)

What good is a server if it is read-only and not writable? Well I'm sure those have their uses too, but we would like to write data to our database, how hard can it be?

如果服务器是只读且不可写的,那有什么用? 好吧,我敢肯定它们也有用途,但是我们想将数据写入数据库,这有多难?

First we'll create a MutationRoot struct that will eventually replace our usage of EmptyMutation. Then we will add the diesel insertion query.

首先,我们将创建一个MutationRoot结构, MutationRoot结构最终将取代EmptyMutation的用法。 然后,我们将添加柴油插入查询。

// graphql_schema.rs
    
    // ...
    
    pub struct MutationRoot;
    
    #[juniper::object]
    impl MutationRoot {
      fn create_member(data: NewMember) -> Member {
        let connection = establish_connection();
        diesel::insert_into(members::table)
          .values(&data)
          .get_result(&connection)
          .expect("Error saving new post")
      }
    }
    
    #[derive(juniper::GraphQLInputObject, Insertable)]
    #[table_name = "members"]
    pub struct NewMember {
      pub name: String,
      pub knockouts: i32,
      pub team_id: i32,
    }

As GraphQL mutations typically go, we define an input object called NewMember and make it the argument of the create_member function. Inside this function, we establish a connection and call the insert query on the members table, passing the entire input object.

作为GraphQL突变通常去,我们定义称为输入对象NewMember并使其的说法create_member功能。 在此函数内部,我们建立连接并在成员表上调用插入查询,并传递整个输入对象。

It is super convenient that Rust allows us to use the same structs for GraphQL input objects as well as Diesel insertable objects.

Rust允许我们为GraphQL输入对象以及Diesel可插入对象使用相同的结构是非常方便的。

Let me make this a little more clear, for the NewMember struct:

对于NewMember结构,让我更清楚一点:

  • we derive juniper::GraphQLInputObject in order to create a input object for our GraphQL schema

    我们派生juniper::GraphQLInputObject以便为我们的GraphQL模式创建输入对象

  • we derive Insertable in order to let Diesel know that this struct is valid input for an insertion SQL statement

    我们派生Insertable以便让Diesel知道此结构对于插入SQL语句是有效输入

  • we add the table_name attribute so that Diesel knows which table to insert it in

    我们添加了table_name属性,以便Diesel知道在哪个表中插入该表

There is a lot of magic going on here. This is what I love about Rust, it has great performance but the code has features like macros and derived traits to abstract away boilerplate and add functionality.

这里有很多魔术 。 这就是我喜欢Rust的原因,它具有出色的性能,但是代码具有诸如宏和派生特征之类的功能,可以抽象出样板并添加功能。

Finally, at the bottom of the file, add the MutationRoot to the schema:

最后,在文件底部,将MutationRoot添加到架构:

// graphql_schema.rs
    
    pub type Schema = RootNode<'static, QueryRoot, MutationRoot>;
    
    pub fn create_schema() -> Schema {
      Schema::new(QueryRoot {}, MutationRoot {})
    }

I hope that everything is there, we can test out all of our queries and mutations thus far now:

我希望一切都在那里,我们可以测试到目前为止的所有查询和变异:

# GraphiQL
    
    mutation CreateMemberMutation($data: NewMember!) {
      createMember(data: $data) {
        id
        name
        knockouts
        teamId
      }
    }
    
    # example query variables
    # {
    #   "data": {
    #     "name": "Samus",
    #     "knockouts": 19,
    #     "teamId": 1
    #   }
    # }

If that mutation ran successfully, you can pop open a bottle of champagne as you are on your way to building performant and type-safe GraphQL Servers with Rust.

如果该突变成功运行,则在使用Rust构建高性能和类型安全的GraphQL服务器的过程中,您可以弹出一瓶香槟。

谢谢阅读 (Thanks For Reading)

I hope you have enjoyed this article, I also hope that it gave you some sort of inspiration for your own work.

我希望您喜欢这篇文章,也希望它给您自己的工作带来一些启发。

If you'd like to keep up with the next time I drop an article in the realm of Rust, ReasonML, GraphQL, or software development at large, feel free to give me a follow on Twitter, dev.to, or on my website at ianwilson.io.

如果您想在下一次我跟上Rust,ReasonML,GraphQL或整个软件开发领域的文章时保持关注,请随时在Twitterdev.to或我的网站上关注我在ianwilson.io

The source code is here github.com/iwilsonq/rust-graphql-example.

源代码在这里github.com/iwilsonq/rust-graphql-example

其他整洁的阅读材料 (Other Neat Reading Material)

Here are some of the libraries we worked with here. They have great documentation and guides as well so be sure to give them a read :)

这是我们在这里使用的一些库。 他们也有出色的文档和指南,因此请务必阅读:)

翻译自: https://www.freecodecamp.org/news/building-powerful-graphql-servers-with-rust/

vscode构建rust

 类似资料: