Building a GraphQL API with Nitric

Use the Nitric framework to easily build and deploy a serverless GraphQL API for AWS, Google Cloud, or Azure

What we'll be doing

GraphQL APIs rely on only one HTTP endpoint, which means that you want it to be reliable, scalable, and performant. By using serverless compute such as Lambda, the GraphQL endpoint can be auto-scaling, whilst maintaining performance and reliability.

We'll be using Nitric to create a GraphQL API, that can be deployed to a cloud of your choice, gaining the benefits of serverless compute.

  1. Create the GraphQL Schema
  2. Write Resolvers
  3. Create handler for GraphQL requests
  4. Run locally for testing
  5. Deploy to a cloud of your choice

Video

Prerequisites

Getting Started

Build the GraphQL Schema

GraphQL requests are type safe, and so they require a schema to be defined to validate queries against. Lets first add the graphql module from npm.

yarn add graphql

We can then import buildSchema, and write out the schema.

import { buildSchema } from 'graphql';
const schema = buildSchema(`
type Profile {
id: String!
name: String!
age: Int!
homeTown: String!
}
input ProfileInput {
name: String!
age: Int!
homeTown: String!
}
type Query {
getProfiles: [Profile]
getProfile(id: String!): Profile
}
type Mutation {
createProfile(profile: ProfileInput!): Profile
updateProfile(id: String!, profile: ProfileInput!): Profile
}
`);

We will also define a few types to mirror the schema definition.

interface Profile {
id: string;
name: string;
age: number;
homeTown: string;
}
type ProfileInput = Omit<Profile, 'id'>;

Create Resolvers

We can create a resolver object for use by the graphql handler.

const resolvers = {
getProfiles,
getProfile,
createProfile,
updateProfile,
};

These functions don't exist, so we'll have to define them. But first lets define a collections resource for the functions to operate against.

import { collection } from '@nitric/sdk';
const profiles = collection<ProfileInput>('profiles').for('reading', 'writing');

We can then use the collection within these functions. Each resolver will receive an args object which holds the graphql arguments from the query.

An example of this is converting the GraphQL query function:

updateProfile(id: String!, profile: ProfileInput!): Profile

Into typescript:

const updateProfile = async ({ id, profile }): Promise<Profile> => {};

Create a profile

const createProfile = async ({ profile }): Promise<Profile> => {
const id = uuid();
await profiles.doc(id).set(profile);
return {
id,
...profile,
};
};

Update a profile

const updateProfile = async ({ id, profile }) => {
await profiles.doc(id).set(profile);
return {
id,
...profile,
};
};

Get all profiles

const getProfiles = async (): Promise<Profile[]> => {
const result = await profiles.query().fetch();
return result.documents.map((doc) => ({
id: doc.id,
...doc.content,
}));
};

Get a profile by its ID

const getProfile = async ({ id }): Promise<Profile> => {
const profile = await profiles.doc(id).get();
return {
id,
...profile,
};
};

GraphQL Handler

We'll define an api to put our handler in.

const profileApi = api('public');

This api will only have one endpoint, which will handle all the requests.

import { graphql, buildSchema } from 'graphql';
...
profileApi.post('/', async (ctx) => {
const { query, variables } = ctx.req.json();
const result = await graphql({
schema: schema,
source: query,
rootValue: resolvers,
});
return ctx.res.json(result);
})

Run it!

Now that you have an API defined with a handler for the GraphQL requests, it's time to test it out locally.

Test out your application with the run command:

nitric run

Note: run starts a container to act as an API gateway, as well as a container for each of the services.

SUCCESS Configuration gathered (3s)
SUCCESS Created Dev Image! (2s)
SUCCESS Started Local Services! (2s)
SUCCESS Started Functions! (1s)
Local running, use ctrl-C to stop
Api | Endpoint
public | http://localhost:9001/apis/public

Once it starts, the application will receive requests via the API port.

Pressing 'Ctrl-C' will end the application.

We can use cURL, postman or any other HTTP Client to test our application, however it's better if the client has GraphQL support.

GraphQL Queries

query {
getProfiles {
id
name
age
homeTown
}
getProfile(id: "1234") {
id
name
age
homeTown
}
}

And here is the syntax for mutating:

mutation {
createProfile(profile: {
name: "Tony Stark",
age: 53,
homeTown: "Manhattan, New York City"
}){
id
name
age
homeTown
}
updateProfile(
id: "1234",
profile: {
name: "Peter Parker",
age: 22,
homeTown: "Queens, New York City"
}
){
id
}
}

Get all Profiles using cURL

curl --location -X POST \
'http://localhost:9001/apis/public/' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query { getProfiles { id name age homeTown }}","variables":"{}"}'
{
"data": {
"getProfiles": [
{
"id": "3f70ca58-25ed-4e88-8a45-eea1fbbb45d8",
"name": "Tony Stark",
"age": 53,
"homeTown": "Manhattan, New York City"
},
{
"id": "9c53bd95-199c-4151-a2a6-0da3ae24c29d",
"name": "Peter Parker",
"age": 22,
"homeTown": "Queens, New York City"
},
{
"id": "9ff191b0-0fbe-4e49-b944-85e79b5caa21",
"name": "Steve Rogers",
"age": 105,
"homeTown": "New York City"
}
]
}
}

Get a single profile

curl --location -X POST \
'http://localhost:9001/apis/public/' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query { getProfile(id: \"3f70ca58-25ed-4e88-8a45-eea1fbbb45d8\") { id name age homeTown }}","variables":"{}"}'
{
"data": {
"getProfile": {
"id": "3f70ca58-25ed-4e88-8a45-eea1fbbb45d8",
"name": "Tony Stark",
"age": 53,
"homeTown": "Manhattan, New York City"
}
}
}

Create a profile

curl --location -X POST \
'http://localhost:9001/apis/public/' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"mutation { createProfile(profile: { name: \"Tony Stark\", age: 53, homeTown: \"Manhattan, New York City\" }){ id name age homeTown }}","variables":"{}"}'
{
"data": {
"getProfile": {
"id": "3f70ca58-25ed-4e88-8a45-eea1fbbb45d8",
"name": "Tony Stark",
"age": 53,
"homeTown": "Manhattan, New York City"
}
}
}

Update a profile

curl --location -X POST \
'http://localhost:9001/apis/public/' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"mutation { updateProfile(id: \"3f70ca58-25ed-4e88-8a45-eea1fbbb45d8\",profile: { name: \"Peter Parker\", age: 22, homeTown: \"Queens, New York City\" }){ id name age homeTown }}","variables":"{}"}'
{
"data": {
"getProfile": {
"id": "3f70ca58-25ed-4e88-8a45-eea1fbbb45d8",
"name": "Peter Parker",
"age": 22,
"homeTown": "Queens, New York City"
}
}
}

Deploy to the cloud

Setup your credentials and any other cloud specific configuration:

Create a stack - a collection of resources identified in your project which will be deployed.

nitric stack new
? What do you want to call your new stack? dev
? Which Cloud do you wish to deploy to? aws
? select the region us-east-1

To undeploy run the following command:

nitric down -s dev