How to
Software development
Written by
Alex Harvey
Date
2 years ago
Read time
7 minutes
REST has been the go-to architectural choice for developing APIs in the past two decades or so. Its advantages are that it’s quick to learn, easy to develop and reliable when implemented. But does it come at a cost? And is there something better that we can use, that can enhance developer experience, increase development speed and ultimately get features out to your customers quicker? We’ll be answering that question in this blog post by looking at GraphQL, more specifically how to migrate from a RESTful API to TypeGraphQL.
If you exist in the tech world it’s likely you’ve heard the term API, but what does it actually mean? API stands for Application Programming Interface
, which isn’t particularly enlightening, but essentially an API is a set of protocols used to design, build and integrate application software. By following these protocols, APIs allow different software services to communicate with each other. These software services don’t have to be from the same company either. If you are developing software and use any SaaS platforms, it’s likely you send and receive data from them using their API.
A good example of this is Google’s weather panel, which shows when you search for a city’s weather in Google. The data that Google comes weather.com, which Google fetches using the their API.
The kind of APIs we’re looking at today are web APIs, which allow developers to send and receive data between a server and a browser — usually in JSON format — to build applications.
A REST (representational state transfer) API, also known as a RESTful API, is an API that conforms to the constraints of the REST architectural style, created by Roy Fielding. The most important of these are:
Statelessness: The server does not store any information or context about the client between requests, meaning the request has everything it needs for the server to process it.
Client-server separation: The client and server are completely separate, meaning the server parses requests and produces responses, whereas the client generates requests and determines how to display responses to a user.
Resource identifiers: REST APIs use Uniform Resource Identifiers (URIs) to address resources (also known as endpoints).
There are a few problems with REST, which can have a negative impact on frontend development experience and overall application speed. These include:
Multiple requests: RESTful APIs separate their endpoints using resource identifiers, meaning if we want resources from different places, we need to send request to multiple endpoints, which increases load times.
Over fetching data: Because of client-server separation, the client has no say in what data they will get when they hit a particular endpoint. This can result in the client receiving more data than they actually need, which again will increase load times.
Unknown structure: For the same reason as above, when a client requests data from an endpoint, it’s possible that they’ll have no knowledge of how that data is structured until they actually request the data. This can result in negative developer experience.
GraphQL, developed by Facebook, describes itself as “a query language for your API”. It provides a fast, stable and scalable means to query your API in a predictable way. It comes at a cost of making your backend slightly more complex, but with the benefit of simplifying your frontend, speeding up your app and solving the problems listed above:
Single endpoint: GraphQL is served over HTTP via a single endpoint, allowing us to fetch any data required from this endpoint using queries and mutations (as we’ll see later).
Request what is needed: With GraphQL the client specifies what resources they need and GraphQL returns only these resources. No more, no less.
Typed schema: With GraphQL we create a schema which fully defines all available data and its types, which means we know exactly what we can request before we actually request it.
Let’s now take a look at basic RESTful API setup and examine how we can migrate it over to GraphQL.
Let’s take a look at a simple REST API that we have set up. This API returns details about Pokemon, stored in a Postgres database, using three routes:
GET /pokemon
: responds to a GET request returning all details of all Pokemon in the db
GET /pokemon/id
: responds to a GET request returning all details of a single Pokemon from its id
POST /pokemon
: responds to a POST request that creates a new Pokemon in the db
We’ve implemented the above in a TypeScript Express app that uses Prisma to interact with the database. Here are our three routes:
// src/routes/pokemon.ts import { Router } from 'express'; import prisma from '../prisma'; const router = Router(); router.get("/pokemon", async (req, res, next) => { const pokemon = await prisma.pokemon.findMany(); res.json(pokemon); }); router.get("/pokemon/:id", async (req, res, next) => { const pokemon = await prisma.pokemon.findFirst({ where: { id: req.params.id }}); if (pokemon) { res.json(pokemon); } else { res.status(404).json({ message: "Pokemon not found" }); } }); router.post("/pokemon", async (req, res, next) => { const { data } = req.body; await prisma.pokemon.create({ data }); res.status(201); }); export default router;
In a production app you’d want to add error handling and type checking, but for this simple example the above will suffice. We have our Express app set up in the file below:
// index.ts import express from 'express'; import Pokemon from './src/routes/pokemon'; const app = express(); app.listen(3000, () => console.log(`App listening on port 3000`)); app.use(Pokemon);
You might see how our above app might be problematic. Say we want to fetch the names of all the Pokemon from our server. Currently to do so, we’d have two choices:
Use the first endpoint to fetch all details of all Pokemon, then filter the resulting data on the frontend to contain only the names. This would result in a massive over fetch of data (there are 801 Pokemon in our database!)
Add a new endpoint that returns only the names of Pokemon. This would take additional developer effort, especially considering in a production app we’d also want to add test files. And then what happens if we want to access only a different Pokemon property? We want to avoid adding an endpoint for each one!
Luckily this problem is easily solved by migrating to GraphQL, which we’ll go through step by step. We’ll be using TypeGraphQL to do this, which is an implementation of GraphQL that allows you to write your whole schema in TypeScript. If you’re using NodeJS, like us, you’ll need to add some dependencies:
yarn add graphql class-validator type-graphql reflect-metadata apollo-server-express apollo-server-core typegraphql-prisma graphql-scalars graphql-fields @types/graphql-fields tslib yarn add -D express-graphql
Note that you’ll need to set the following in your tsconfig.json
:
{ "compilerOptions": { "target": "es2018", "module": "commonjs", "lib": ["es2018", "esnext.asynciterable"], "strict": true, "esModuleInterop": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "strictPropertyInitialization": false } }
Now let’s start implementing GraphQL! The first thing we need to do is type out our Pokemon instance, so we can tell GraphQL exactly what to expect when we return an instance of this later. Our Pokemon instance looks like this:
// src/schema/pokemon/pokemon.type.ts import { Field, ObjectType } from "type-graphql"; @ObjectType() export class Pokemon { @Field() id: number; @Field() name: string; @Field() pokedex_number: number; @Field() attack: number; @Field() defense: number; @Field() hp: number; @Field() sp_attack: number; @Field() sp_defense: number; @Field() speed: number; }
Next we need to create a resolver, which is the collections of functions that are responsible for fetching the data for our GraphQL queries. You can see here that the business logic corresponds directly to the logic we implemented in our API routes earlier - our queries correspond to the GET requests and our mutation corresponds to the POST request:
// src/schema/pokemon/pokemon.resolver.ts import { Arg, Query, Resolver } from "type-graphql"; import { Pokemon } from "./pokemon.type"; import prisma from "../../prisma"; @Resolver() export class PokemonResolver { @Query(() => [Pokemon]) async allPokemon(): Promise<Pokemon[]> { const pokemon = await prisma.pokemon.findMany(); return pokemon; } @Query(() => Pokemon) async pokemon( @Arg("id") id: number, ): Promise<Pokemon | null> { const pokemon = await prisma.pokemon.findFirst({ where: { id }}); if (pokemon) { return pokemon; } return null; } @Mutation(() => Boolean) async addPokemon(@Arg("data") newPokemon: PokemonInput): Promise<boolean> { await prisma.pokemon.create({ data: newPokemon }); return true; } }
We also need to modify our Express app to allow GraphQL requests. We can also remove the REST endpoints previously used:
import express from "express"; import http from 'http'; import { buildSchema } from "type-graphql"; import { PokemonResolver } from "./src/schema/pokemon/pokemon.resolver"; import { ApolloServer } from 'apollo-server-express'; import { ApolloServerPluginDrainHttpServer } from "apollo-server-core"; const bootstrap = async () => { const schema = await buildSchema({ resolvers: [PokemonResolver], }); const app = express(); const httpServer = http.createServer(app); const server = new ApolloServer({ schema, plugins: [ApolloServerPluginDrainHttpServer({ httpServer })], }); await server.start(); server.applyMiddleware({ app, path: '/graphql' }); await new Promise<void>(resolve => httpServer.listen({ port: 3000 }, resolve)); console.log(`🚀 Server ready at http://localhost:3000${server.graphqlPath}`); }; bootstrap();
Running the above, you’ll see the following message Server is running, GraphQL Playground available at http://localhost:3000/
. If we navigate to this url, you’ll see your GraphQL server running:
We can now easily query out server to return a list of all the Pokemon’s names in our database
Querying for a single Pokemon is similar, but we need to provide the name in the Variables
tab at the bottom of the page
And we create a Pokemon in the database via a mutation in a similar way:
GraphQL will complain if we miss one of the required fields in the data
object before it hits the database, thanks to its safely-typed schema.
And there you have it, how to quickly start migrating your RESTful Express API over to TypeGraphQL. The great thing about this is that we can take a strangulation approach, slowly adopting GraphQL into our production apps as we decrease the usage of our REST endpoints, meaning we don’t have to make massive and potentially breaking changes. For more details on this approach check out this article by author and developer Martin Fowler.
Once you’ve started implementing GraphQL in your backend, you can begin requesting data using libraries like React Query or Apollo Client. If you need to speed up writing your frontend components, check out our post on Code Generation With Hygen. We hope this post has been useful for you!
Build an admin console in a day with Forest Admin
2 years ago
How to
Add Contentful to your TypeScript project
1 year ago
How to
Software development
What is Hygen and how could it help speed up your project?
2 years ago
How to
Software development
The ultimate component testing suite - Cypress, Storybook and Mock Service Worker
2 years ago
How to
Software development
Subscribe to our newsletter
Be the first to know about our latest updates, industry trends, and expert insights
Your may unsubscribe from these communications at any time. For information please review our privacy policy.