6.8 KiB
title | categories | tags | ||
---|---|---|---|---|
Creating a GraphQL server |
|
|
Creating a GraphQL server
We will use Node.js to create a basic GraphQL server that will serve data from a product database.
Our server will allow us to add products to a database through a mutatation and to query the products that we have added. We will use a JS object instead of a real database.
Create a basic Express server
First we create a basic server in Node using Express:
import express from "express";
app.get("/", (req, res) => {
res.send("Graph QL test project");
});
app.listen(8080, () =>
console.log("Running server on port localhost:8080/graphql")
);
Add GraphQL as middlewear
Next we introduce GraphQL as a piece of Node.js middlewear, with the app.use()
method.
import { graphqlHTTP } from "express-graphql";
app.use(
"/graphql",
graphqlHTTP({
schema: schema,
rootValue: resolvers,
graphiql: true,
})
);
schema
is a reference to our GraphQL schema - the structure of the fields that define our server. This is not yet defined.rootValue
is a reference to our resolvers. This is not yet defined.graphiql
is the GUI tool that will be served from the GraphQL endpoint atlocalhost:8080/graphql
. This tool enables us to interrogate our data using the defined schema and see what data we would get back from frontend queries.
Resolvers
We will specify our resolvers in a dedicated resolver file. In GraphQL you need to define resolvers for both your queries and your mutations.
To achieve this we will have a dummy object as the database containing our products and a class working as a generator function that will create a product object with certain properties, individuated by an id. We will invoke this class to create new products for the database and to retrieve existing products from the database.
First we create the product class and the database object:
// resolvers.js
const productDb = {};
class Product {
constructor(id, { name, description, price, soldout, stores }) {
this.id = id;
this.name = name;
this.description = description;
this.price = price;
this.soldout = soldout;
this.stores = stores;
}
}
Next we define a resolver for read operations. This will receive a product id and return the corresponding product back from the database as an instance of Product
.
// resolvers.js
const resolvers = {
getProducts: ({ id }) => {
return new Product(id, productDb[id]);
},
};
Next we declare a resolver that will handle our mutation: adding new products to the database.
Mutations in GraphQL are the equivalent of
POST
,PUT
, andDELETE
in REST APIs. In other words, they are the means by which we update the data that the GraphQL Server exposes.
// resolvers.js
const resolvers = {
getProducts: ({ id }) => {
return new Product(id, productDb[id]);
},
createProduct: ({ input }) => {
let id = guid; // imagine a hash function here
productDatabase[id] = input;
return new Product(id, input);
},
};
Schema
This handles the backend mechanics of reading from and writing to the database, but we need still need to integrate it with the GraphQL middlewear. We do this through the GraphQL server's schema file.
The GraphQL Schema, defined on the backend, describes the shape of queries that can be run against the GraphQL Server. A schema is a series of fields matched to a type specification. Writing a schema is just like defining a type or interface in TypeScript or a schema in Mongoose.
Define Product
type
First we will define a schema entry for products:
// schema.js
import { buildSchema } from "graphql";
const schema = buildSchema(`
type Product {
id: ID
name: String
description: String
price: Float
soldout: Boolean
stores: [Store]!
}
type Store {
store: String
}
`);
Note that here we define a custom
Store
type that integrates with the theProduct
type as an array of stores. This is a required field, indicated by the!
. Also theID
type is special ...
// TODO: Explain why the ID type is in caps. Is it the equivalent of the primary key?
Define Query
method
So far we have established the types necessary to service our getProduct
resolver but we have not provided a declared means of querying. We do this by declaring a Query
type that will invoke the getProduct
resolver we defined earlier. Now the server knows that when a Query
is run, it must use that resolver:
const schema = buildSchema(`
...
type Query {
getProducts(id: ID)
}
`);
Define Mutation
So far we have defined the fields necessary to query the GraphQL server using the getProduct
resolver but we have not yet provided a way to mutate the data by adding new products. We need to integrate our createProduct
resolver.
We do this by defining a Mutation
type that references the createProduct
resolver. And we type its paramter and return value:
const schema = buildSchema(`
...
type Mutation {
createProduct(input: ProductInput): Product
}
input ProductInput {
id: ID
name: String
description: String
price: Float
solout: Boolean
stores: [StoreInput]!
}
type StoreInput {
store: String
}
`);
Note that the
input
parameter to the mutation and theinput
keyword are closely coupled. Note also that althoughProductInput
/Product
andStore
/StoreInput
are identical in terms of their shape, we must still create dedicated new types. We cannot mix the types from the different resolvers.
Using the server
Our server is now complete and will allow us to send and receive data of the following shape:
{
"id": 1234,
"name": "Product A",
"description": "This is Product A",
"price": 34.99,
"soldout": false,
"stores": [
{
"store": "London"
},
{
"store": "Sheffield"
},
{
"store": "Lincoln"
}
]
}
We will now switch to the client-side and see how we can go about adding and querying products.
Adding new products through a mutation
We can invoke our mutation resolver by sending the following query to the server:
mutation {
createProduct(
input: {
name: "Widget4"
description: "Lorem ipsum"
price: 39.99
soldout: false
stores: [{ store: "London" }]
}
) {
price
name
soldout
}
}
This is represented in GraphiQL as follows:
We should always return something, even if we are applying mutation, hence we add the properties at the bottom as the ones we want to return.
Returning a product through a query
// Add new image of this working in GraphiQL
// TODO: Explain input types (https://graphql.org/graphql-js/mutations-and-input-types/)
// TODO: Explain enums