In this tutorial, we will lead you through the process of creating a simple GraphQL backend application.
We'll use GQLoom
with your favorite schema builder to define the GraphQL Resolver and Schema to build a simple cattery application that can query the cats in the cattery and can add new cats to the cattery.
You are going to use the following technologies:
GQLoom
:define GraphQL Schema and resolvers more simply and efficiently.You can use a familiar Schema Builder such as Valibot, Zod, or even just use GraphQL.js.
Before you start, make sure you have the following software installed:
This tutorial assumes that you have a basic knowledge of TypeScript, Node.js, GraphQL, and some knowledge of Valibot or Zod. If you are a beginner, we suggest you learn these basics first.
First, we need to create a new Node.js project.
Open your command line and run the following command:
In the above command: we created a new directory named cattery
and entered it. Then, we initialize a new Node.js project using the npm init -y
command and automatically generate a default package.json
file.
Next, we need to install some necessary dependencies.
In this step, we installed TypeScript, the type definition for Node.js, and tsx. tsx is a tool for running TypeScript in Node.js.
We also installed graphql and graphql-yoga to help us run the GraphQL service.
Now, create a new TypeScript profile using the following command:
First, we add a dev
script to package.json
to start our application:
Then, we create an src/index.ts
file and add the following code:
In the code above: we define our GraphQL Resolver using resolver
and query
, we weave CatResolver
into a GraphQL Schema with the weave
function, and we start our GraphQL service with graphql-yoga
.
Now, you can run the following command to start your application:
You should see output similar to the following:
You can open your browser and visit http://localhost:4000/graphql and you will see a GraphQL playground where you can test your GraphQL queries:
For example, when we input:
You should see the following output:
Now that you've successfully started your GraphQL service, let's try to construct slightly more complex functionality.
Next, we define a Cat
type with a name
field and a birthDate
field.
In the above code, we have used v.object
to define the Cat
type:
It has a __typename
field with a value of “Cat”
, which will be used as the name of this object when weaving the GraphQL Schema, and we also set __typename
to nullish
so that we don't have to carry the __typename
property for each instance of Cat
object at runtime This avoids having to carry the __typename
property for each instance of Cat
at runtime;
There is also a name
field and a birthDate
field, both of which are of type string.
Finally, we easily get the output type of Cat
using v.InferOutput
and name it ICat
.
GQLoom
will weave the Cat
type we just defined into a GraphQL Schema:
To manage our data, we simply use a Map object to store Cat
instances:
In this tutorial, we are storing the data directly using JavaScript's Map
object for the purpose of code simplicity. This will store the data in memory and when the server restarts, the data will be lost.
In practice, you may need to use a more robust data persistence storage solution, such as a database.
The query
operation is the entry point to the GraphQL Schema that allows clients to query data.
Now, let's go back to the beginning with CatResolver
and add a query
operation to it called cats
which returns all Cat
instances:
In the code above, we use the resolver
function to define CatResolver
and add a query
operation named cats
that returns all Cat
instances.
The query
function takes two arguments:
cats
, which you can pass directly into the valibot
schema, in this case v.array(Cat)
; the second argument is a parser function, which returns all instances of Cat
.cats
, where we use the Array.from
function to convert catMap
to an array and use it as the return value for cats
.In addition, we also weave together CatResolver
and HelloResolver
using the weave
function to create the final GraphQL Schema.
Let's try to access the cats
operation in the playground:
You should see the following output:
Next, we define a query
operation called cat
that takes a name
argument and returns the instance of Cat
matching that name
:
In the code above, we add a query
operation called cat
to CatResolver
.
Similar to cats
, the query
function used to build cat
takes as its first argument the nullish
type of Cat
, which means that the return value of the cat
operation can be of type null
or Cat
.
In the second argument, we still pass a parser function, but this time we pass an additional input
parameter that defines the input type for the cat
operation.
The input
parameter is an object containing an attribute named name
, which is of type string
. When the cat
operation is accessed, GQLoom
internally calls the parse
function of valibot
to ensure that the value of the name
parameter matches the string
type.
In the resolve function, we get the value of the name
parameter from the first parameter of the resolve
function, TypeScript infers the type of the name
parameter to be string
, and we use the catMap.get
method to get the instance of Cat
that corresponds to the name
, which is used as the return value of the cat
operation. as the return value of the cat
operation.
Let's try to access the cat
operation in the playground:
You should see the following output:
The mutation
operation is used to modify data, such as creating, updating, or deleting data.
Now, let's add a mutation
operation named createCat
to CatResolver
that takes a name
parameter and returns a Cat
instance.
In the code above, we have added a mutation
operation named createCat
to CatResolver
.
The input to the mutation
function is the same as the query
function.
Here, the createCat
operation has a return type of Cat
and accepts as input two parameters, name
and birthDate
, both of which are of type string
.
In the parser function, we can easily get the values of the name
and birthDate
parameters from the first parameter, TypeScript will infer their types for us, and then we create a new Cat
instance, add it to the catMap
, and finally return the Cat
instance.
Let's try to create a new Cat
instance in the playground:
You should see results similar to the following:
Let's use the cats
query to get all Cat
instances:
You should see results similar to the following:
Now, let's try to define an age
field for the Cat
type.
The age
field is not stored in the Cat
instance, but is calculated on each query.
In the code above, we have added a field
named age
to CatResolver
.
Notice that we used the resolver.of
function instead of resolver
.
The first argument to the resolver.of
function is an object Schema, in this case Cat
, which will be used as the source
type for CatResolver
;
In the second argument, we still pass in query
, mutation
, and field
to define CatResolver
.
In the field
named age
, we use v.pipe(v.number(), v.integer())
to define the type of age
, and GQLoom
will weave the age
field into a GraphQL Int
type. Note that GQLoom
does not execute a parse
step on the output of a parser function by default, because the results produced inside a parser function are usually controllable and follow the TypeScript-derived type.
In the resolve function, we easily get the value of the cat
instance from the first parameter, TypeScript will infer its type for us, then we convert the cat
instance's birthDate
field to a Date
instance and calculate the difference between the current year and the year of the birthDate
, and finally return the difference as the cat's age.
Let's try to visit the cat
operation in the playground:
You should see the following output:
We can add an input
object to a field
, which will be used as the input parameter for that field
.
In the above code, we have added an input
object to the age
field, which contains a field named year
of type Int
, which uses the current year as the default value if no year
input is provided.
In the field
parse function, we can easily get the value of year
from the second argument.
Let's try to access the cat
operation in the playground:
You should see the following output:
Very well, we have written a simple GraphQL APP that contains a CatResolver
.
In the example we just learned:
query
, mutation
in resolver
;valibot
, zod
or graphql.js
;query
, mutation
, field
;CatResolver
and HelloResolver
into a GraphQL Schema using the weave
function and launch our GraphQL APP using graphql-yoga
.Valibot
to build more complex GraphQL objects and advanced types such as Union, Interface, and Enum.Zod
to build more complex GraphQL objects and advanced types like Union, Interface, and Enum.