Nitric logo

Guides

Building a GraphQL API with Nitric

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

Here's a video of this guide built with Node.js:

Serverless GraphQL on any Cloud

Prerequisites

TypeScript
Python

Getting started

We’ll start by creating a new project for our API.

nitric new

Create a project, name it and select your preferred starter template.

TypeScript
Python
? What is the name of the project? my-profile-api
? Choose a template: official/TypeScript - Starter

Next, open the project in your editor of choice.

> cd my-profile-api

Make sure all dependencies are resolved:

TypeScript
Python

Using NPM:

npm install

The scaffolded project should have the following structure:

TypeScript
Python
+--functions/
|  +-- hello.ts
+--node_modules/
|  ...
+--nitric.yaml
+--package.json
+--README.md

You can test the project to verify everything is working as expected:

npm run dev

Note: the dev script starts the Nitric Server using nitric start, which provides local interfaces to emulate cloud resources, then runs your functions and allows them to connect.

If everything is working as expected you can now delete all files in the functions/ folder, we'll create new functions in this guide.

Build the GraphQL Schema

GraphQL requests are typesafe, and so they require a schema to be defined to validate queries.

TypeScript
Python

Let's first add the GraphQL and uuid module from NPM.

npm install graphql
npm install uuidv4

Create a new file named 'graphql.ts' in the functions folder. We can then import buildSchema, and write out the schema.

import { buildSchema } from 'graphql';
import { uuid } from 'uuidv4';

const schema = buildSchema(`
  type Profile {
    pid: String!
    name: String!
    age: Int!
    home: String!
  }

  input ProfileInput {
    name: String!
    age: Int!
    home: String!
  }

  type Query {
    getProfiles: [Profile]
    getProfile(pid: String!): Profile
  }

  type Mutation {
    createProfile(profile: ProfileInput!): Profile
    updateProfile(pid: String!, profile: ProfileInput!): Profile
  }
`);

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

interface Profile {
  pid: string;
  name: string;
  age: number;
  home: string;
}

type ProfileInput = Omit<Profile, 'pid'>;

Define a Collection

Lets define a collections resource for the resolvers get/set data from.

TypeScript
Python
import { collection } from '@nitric/sdk';

const profiles = collection<ProfileInput>('profiles').for('reading', 'writing');

Create Resolvers

TypeScript
Python

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.

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 ({ pid, profile }): Promise<Profile> => {};

Create a profile

TypeScript
Python
const createProfile = async ({ profile }): Promise<Profile> => {
  const pid = uuid();

  await profiles.doc(pid).set(profile);

  return {
    pid,
    ...profile,
  };
};

Update a profile

TypeScript
Python
const updateProfile = async ({ pid, profile }) => {
  await profiles.doc(pid).set(profile);

  return {
    pid,
    ...profile,
  };
};

Get all profiles

TypeScript
Python
const getProfiles = async (): Promise<Profile[]> => {
  const result = await profiles.query().fetch();

  return result.documents.map((doc) => ({
    pid: doc.id,
    ...doc.content,
  }));
};

Get a profile by its ID

TypeScript
Python
const getProfile = async ({ pid }): Promise<Profile> => {
  const profile = await profiles.doc(pid).get();

  return {
    pid,
    ...profile,
  };
};

GraphQL Handler

We'll define an api to put our handler in. This api will only have one endpoint, which will handle all the requests.

TypeScript
Python

Update the imports to include api and declare the api.

import { api, collection } from '@nitric/sdk';

const profileApi = api('public');

Then add the api handler.

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.

TypeScript
Python

Test out your application with the following command:

npm run dev

Note: the dev script in the template starts the Nitric Server using nitric start and runs your functions.

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

Pressing ctrl + a + k will end the application.

GraphQL Queries

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

Get all Profiles using cURL

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

You can then deploy using the following command:

nitric up

To undeploy run the following command:

nitric down
Previous
Nitric Deploy