Guides

Building your first API with Nitric

What we'll be doing

  1. Use Nitric to create an API to create and update profiles
  2. Create handlers for the following API operations
MethodRouteDescription
GET/profiles/{id}Get a specific profile by its Id
GET/profilesList all profiles
POST/profilesCreate a new profile
DELETE/profiles/{id}Delete a profile
PUT/profiles/{id}Update a profile
  1. Run locally for testing
  2. Deploy to a cloud of your choice
  3. (Optional) Add handlers for the following API operations
MethodRouteDescription
GET/profiles/{id}/image/uploadGet a profile image upload URL
GETprofiles/{id}/image/downloadGet a profile image download URL

Video

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

Build and Deploy a REST API for 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.

Building the Profile API

TypeScript
Python

In this example we'll use UUIDs to create unique IDs to store profiles against, let's start by adding a library to help with that:

npm install uuidv4

Next, let's start building the profiles API. Create a file named 'profiles.ts' in the functions directory and add the following:

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

// Create an api named public
const profileApi = api('public');

// Access profile collection with permissions
const profiles = collection('profiles').for('writing', 'reading');

Here we're creating:

  • An API named public,
  • A collection named profiles and giving our function permission to read and write to that collection.

From here, let's add some features to that function that allow us to work with profiles.

Note: You could separate some or all of these request handlers their own functions if you prefer. For simplicity we'll group them together in this guide.

Create profiles with POST

TypeScript
Python
profileApi.post('/profiles', async (ctx) => {
  let id = uuid();
  let name = ctx.req.json().name;
  let age = ctx.req.json().age;
  let homeTown = ctx.req.json().homeTown;

  // Create the new profile
  await profiles.doc(id).set({ name, age, homeTown });

  // Return the id
  ctx.res.json({
    msg: `Profile with id ${id} created.`,
  });
});

Retrieve a profile with GET

TypeScript
Python
profileApi.get('/profiles/:id', async (ctx) => {
  const { id } = ctx.req.params;

  // Return the profile
  try {
    const profile = await profiles.doc(id).get();
    return ctx.res.json(profile);
  } catch (error) {
    ctx.res.status = 404;
    ctx.res.json({
      msg: `Profile with id ${id} not found.`,
    });
  }
});

List all profiles with GET

TypeScript
Python
profileApi.get('/profiles', async (ctx) => {
  // Return all profiles
  ctx.res.json({
    output: await profiles.query().fetch(),
  });
});

Remove a profile with DELETE

TypeScript
Python
profileApi.delete('/profiles/:id', async (ctx) => {
  const { id } = ctx.req.params;

  // Delete the profile
  try {
    profiles.doc(id).delete();
  } catch (error) {
    ctx.res.status = 404;
    ctx.res.json({
      msg: `Profile with id ${id} not found.`,
    });
  }
});

Ok, let's run this thing!

Now that you have an API defined with handlers for each of its methods, it's time to test it locally.

TypeScript
Python
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.

Once it starts, the application will receive requests via the API port. You can use cURL, Postman or any other HTTP client to test the API.

We will keep it running for our tests. If you want to update your functions, just save them, they'll be reloaded automatically.

Test your API

Update all values in {} and change the URL to your deployed URL if you're testing on the cloud.

Create Profile

curl --location --request POST 'http://localhost:4001/profiles' \
--header 'Content-Type: text/plain' \
--data-raw '{
    "name": "Peter Parker",
    "age": "21",
    "homeTown" : "Queens"
}'

Fetch Profile

curl --location --request GET 'http://localhost:4001/profiles/{id}'

Fetch All Profiles

curl --location --request GET 'http://localhost:4001/profiles'

Delete Profile

curl --location --request DELETE 'http://localhost:4001/profiles/{id}'

Deploy to the cloud

At this point, you can deploy what you've built to any of the supported cloud providers. To do this start by setting up your credentials and any configuration for the cloud you prefer:

Next, we'll need to create a stack. A stack represents a deployed instance of an application, which is a collection of resources defined in your project. You might want separate stacks for each environment, such as stacks for dev, test and prod. For now, let's start by creating a dev stack.

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

AWS

Note: You are responsible for staying within the limits of the free tier or any costs associated with deployment.

We called our stack dev, let's try deploying it with the up command

nitric up
┌───────────────────────────────────────────────────────────────┐
| API  | Endpoint                                               |
| main | https://XXXXXXXX.execute-api.us-east-1.amazonaws.com   |
└───────────────────────────────────────────────────────────────┘

When the deployment is complete, go to the relevant cloud console and you'll be able to see and interact with your API.

To tear down your application from the cloud, use the down command:

nitric down

Optional - Add profile image upload/download support

If you want to go a bit deeper and create some other resources with Nitric, why not add images to your profiles API.

Access profile buckets with permissions

Define a bucket named profilesImg with reading/writing permissions

TypeScript
Python
const profilesImg = bucket('profilesImg').for('reading', 'writing');

Get a URL to upload a profile image

TypeScript
Python
profileApi.get('/profiles/:id/image/upload', async (ctx) => {
  const id = ctx.req.params['id'];

  // Return a signed url reference for upload
  const photoUrl = await profilesImg
    .file(`images/${id}/photo.png`)
    .getUploadUrl();
  ctx.res.json({
    url: photoUrl,
  });
});

Get a URL to download a profile image

TypeScript
Python
profileApi.get('/profiles/:id/image/download', async (ctx) => {
  const id = ctx.req.params['id'];

  // Return a signed url reference for download
  const photoUrl = await profilesImg
    .file(`images/${id}/photo.png`)
    .getDownloadUrl();
  ctx.res.json({
    url: photoUrl,
  });
});

You can also directly redirect to the photo URL.

TypeScript
Python
profileApi.get('/profiles/:id/image/view', async (ctx) => {
  const { id } = ctx.req.params;

  // Create a read-only signed url reference
  const photoUrl = await profilesImg
    .file(`images/${id}/photo.png`)
    .getDownloadUrl();
  ctx.res.status = 303;
  ctx.res.headers['Location'] = [photoUrl];
});

Time to test the updated API

Update all values in {} and change the URL to your deployed URL if you're testing on the cloud.

Get an image upload URL

curl --location --request GET 'http://localhost:4001/profiles/{id}/image/upload'

Using the upload URL with curl

curl --location --request PUT '{url}' \
--header 'content-type: image/png' \
--data-binary '@/home/user/Pictures/photo.png'

Get an image download URL

curl --location --request GET 'http://localhost:4001/profiles/{id}/image/download'