Build your first API for the cloud with Nitric
Run locally or Deploy to AWS, GCP, or Azure
Originally posted on the Nitric site.
Ready to get started building and deploying applications to the cloud? Let's get started with a simple API to learn the basics, and get you started on your journey.
What we'll do
- Use Nitric to create an API to create and update profiles
- Create handlers for the following API operations
- Running locally for testing
- Deploy to a cloud of your choice
- (Optional) Add handlers for the following API operations
Method | Route | Description |
GET | /profiles/{id} | Get a specific profile by its Id |
GET | /profiles | List all profiles |
POST | /profiles | Create a new profile |
DELETE | /profiles/{id} | Delete a profile |
PUT | /profiles/{id} | Update a profile |
GET | /profiles/{id}/image/upload | Get a profile image upload URL |
GET | profiles/{id}/image/download | Get a profile image download URL |
Getting started
This tutorial assumes you have the Nitric CLI installed. If not, follow this quick installation guide.
We’ll start by creating a new project for our API.
nitric new
Create a project name, select the TypeScript template and choose the default glob handler.
? What is the name of the stack? my-profile-api
? Choose a template: official/TypeScript - Starter
? Glob for the function handlers? functions/*.ts
Next, open the project in your editor of choice.
> cd my-profile-api
Make sure all dependencies are resolved with yarn or npm.
yarn install
The scaffolded project should have the following structure:
+--functions/
| +-- hello.ts
+--node_modules/
| ...
+--nitric.yaml
+--package.json
+--README.md
You can test the project scaffold with the run
command.
nitric run
The first time you run a project or function it will take a moment longer to start while Docker image layers are downloaded and cached on your machine.
SUCCESS Configuration gathered (3s)
SUCCESS Created Dev Image! (1s)
SUCCESS Started Local Services! (2s)
SUCCESS Started Functions!(0s)
Local running, use ctrl-C to stop
Api | Endpoint
main | http://localhost:9001/apis/main
The template project is successfully scaffolded.
Since we won't use the example function you can now delete the
functions/hello.ts
file.
Add uuidv4 to your project
We'll need a unique identifier to store our profiles against.
> yarn add uuidv4
Coding our Profile API
Let's start by initializing our profiles api, create a file named 'profiles.ts' within functions and add the following code.
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 are defining the following -
- an API named public,
- a collection named profiles with reading/writing permissions
Create profile with a post method
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 profile with a get method
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.`,
});
}
});
Retrieve all profiles with a get method
profileApi.get('/profiles', async (ctx) => {
// Return all profiles
ctx.res.json({
output: await profiles.query().fetch(),
});
});
Remove profile with a delete method
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.`,
});
}
});
Update profile with a put method
profileApi.put('/profiles/:id', async (ctx) => {
const { id } = ctx.req.params;
const doc = profiles.doc(id);
try {
// Update values provided
const current = await doc.get();
let name = ctx.req.json().name ?? current['name'] ?? '';
let age = ctx.req.json().age ?? current['age'] ?? '';
let homeTown = ctx.req.json().homeTown ?? current['homeTown'] ?? '';
// Create or Update the profile
await doc.set({ name, age, homeTown });
} catch (error) {
ctx.res.status = 404;
ctx.res.json({
msg: `Profile with id ${id} not found.`,
});
}
});
Nitric will automatically infer the required specification and permissions to create an API Gateway - Learn more.
Run it!
Now that you have an API defined with handlers for each of its methods, 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. You can use cURL, Postman or any other HTTP client to test the API.
Pressing 'Ctrl-C' will end the application.
Test your API
Update all {} values and change the URL to your deployed URL if you're testing on the cloud.
Create Profile
curl --location --request POST 'http://127.0.0.1:9001/apis/public/profiles' \
--header 'Content-Type: text/plain' \
--data-raw '{
"name": "Peter Parker",
"age": "21",
"homeTown" : "Queens"
}'
Fetch Profile
curl --location --request GET 'http://127.0.0.1:9001/apis/public/profiles/{id}'
Fetch All Profiles
curl --location --request GET 'http://127.0.0.1:9001/apis/public/profiles'
Update Profile
curl --location --request PUT 'http://127.0.0.1:9001/apis/public/profiles/{id}' \
--header 'Content-Type: text/plain' \
--data-raw '{
"name": "Ben Reily",
"homeTown" : "Las Vegas"
}'
Delete Profile
curl --location --request DELETE 'http://127.0.0.1:9001/apis/public/profiles/{id}'
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
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, lets try deploying it with the up
command
nitric up -s dev
┌───────────────────────────────────────────────────────────────┐
| 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 undeploy run the following command.
nitric down -s dev
Optional - Add profile image upload/download support
Access profile buckets with permissions
Define a bucket named profilesImg with reading/writing permissions
const profilesImg = bucket('profilesImg').for('reading', 'writing');
Get Signed URL to Upload Profile Image
profileApi.get('/profiles/:id/image/upload', async (ctx) => {
const id = ctx.req.params['id'];
// Return a signed url reference for upload
const photo = profilesImg.file(`images/${id}/photo.png`);
const photoUrl = await photo.signUrl(FileMode.Write);
ctx.res.json({
url: photoUrl,
});
});
Get Signed URL to Download Profile Image
profileApi.get('/profiles/:id/image/download', async (ctx) => {
const id = ctx.req.params['id'];
const photo = profilesImg.file(`images/${id}/photo.png`);
// Return a signed url reference for download
const photoUrl = await photo.signUrl(FileMode.Read);
ctx.res.json({
url: photoUrl,
});
});
You can also directly redirect to the photo url.
profileApi.get('/profiles/:id/image/view', async (ctx) => {
const { id } = ctx.req.params;
const photo = profilesImg.file(`images/${id}/photo.png`);
// Create a read-only signed url reference
const photoUrl = await photo.signUrl(FileMode.Read);
ctx.res.status = 303;
ctx.res.headers['Location'] = [photoUrl];
});
Test your API
Update all {} values and change the URL to your deployed URL if you're testing on the cloud.
Get upload image URL
curl --location --request GET 'http://127.0.0.1:9001/apis/public/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 download image URL
curl --location --request GET 'http://127.0.0.1:9001/apis/public/profiles/{id}/image/download'
Summary
That's it! You've got your first API deployed to the cloud. What's next?
We've got other guides on the Nitric website, and a lot of other great features to check out in our docs.