Build a Multi-Cloud API in 10 minutes
Tags: cloud, tutorial, typescript, node
Published: 2021-12-08 03:48:24

Working on a REST API you want to deploy to the cloud, but aren’t sure cloud providers, deployment, scaling, persistence tech, etc.? There's also the big decision of which cloud provider to deploy to AWS, Google Cloud, Microsoft Azure? I can feel the decision fatigue headache already!

[Nitric](https://nitric.io) makes it so those decisions don’t need to be made up front. Get started building your kick ass new API, using a document database, then deploy it to any cloud.

Let's get started!

## Video

You can check also check out the [video version](https://www.youtube.com/watch?v=8Z9S_TWfadI).

> The source code of this tutorial is on <a href="https://github.com/nitrictech/customers-api" target="_blank">GitHub</a>

## What we’ll do

We'll build a simple API that can create and list customers. To help get started we'll use Nitric’s Typescript [Stack](https://nitric.io/docs/intro/concepts/stacks) template as a foundation, this is available via the Nitric CLI.

Next, we'll change the stack by creating new [functions](https://nitric.io/docs/intro/concepts/compute#functions) to handle our customer API requests. To store and access our customers we'll use a document database via the [Nitric SDK](https://nitric.io/docs/intro/concepts/sdks) for Node.

Finally, we'll define our [API](https://nitric.io/docs/reference/project/api-gateways) using <a href="https://swagger.io/specification/" target="_blank">OAS 3.0</a>, run it locally for testing, then you're ready to deploy. 🎉

### Assumptions

Before getting started, ensure you have <a href="https://nodejs.org/en/download/" target="_blank">Node.js</a> and <a href="https://docs.docker.com/get-docker/" target="_blank">Docker</a> installed.

## Getting Started

First, let’s install the Nitric CLI using <a href="https://www.npmjs.com/" target="_blank">npm</a>:

```bash
npm install -g @nitric/cli
```

You can create your Stack by running the `make:stack` command below:

```
nitric make:stack
```

Follow the prompts and select the Typescript template.

Next, open the project in your editor of choice, you should see the following structure:

```text
rest-api
├── common
│   ├── example.ts
│   ├── index.ts
│   └── path.ts
├── functions
│   ├── create.ts
│   ├── list.ts
│   └── read.ts
├── .gitignore
├── api.yaml
├── nitric.yaml
├── package.json
└── yarn.lock
```

The stack template already comes with a working API example, so we'll use this as a starter for our customers API.

## Creating our Function Handlers

To create and list our customers we'll use the [documents API](https://nitric.io/docs/reference/api/documents) from the Nitric SDK. This API has a cloud agnostic syntax, this means it will automatically use Firestore on GCP, DynamoDB on AWS or Cosmos on Azure. Learn once, write once, deploy anywhere.

Time to install our dependencies, from your project directory run:

```bash
yarn install
```

### Type safety

We are using Typescript, so let’s create a Customer interface we can use in our functions to ensure type safety. In the `common` folder, create a new file called `customer.ts` with the following content:

```typescript
// common/customer.ts
export interface Customer {
  name: string;
  email: string;
}
```

> The interface helps with type-safety, we can then enforce it with our API definition.

### Creating Customers

Let's turn the create function into the handler for `POST: /customers`, having it add new customers to a document collection called `customers`. Since new customers will need a unique id, for the purposes of this tutorial we'll generate a uuid to serve as the id (uuidv4 is included as a dependency).

```typescript
// functions/create.ts
import { faas, documents } from "@nitric/sdk";
import { Customer } from "../common";
import { uuid } from "uuidv4";

interface CreateContext extends faas.HttpContext {
  req: faas.HttpRequest & {
    body?: Customer;
  };
}

// Start your function here
faas
  .http(
    faas.json(), //  use json body parser middleware to decode data
    async (ctx: CreateContext): Promise<faas.HttpContext> => {
      const customer = ctx.req.body;

      // generate a new uuid
      const id = uuid();

      // Create a new customer document
      await documents().collection("customers").doc(id).set(customer);

      ctx.res.body = new TextEncoder().encode(
        `Created customer with ID: ${id}`
      );

      return ctx;
    }
  )
  .start();
```

### List Customers

Next, let's update the list function for GET: /customers, which will retrieve all customers:

```typescript
// functions/list.ts
import { faas, documents } from "@nitric/sdk";
import { Customer } from "../common";

// Start your function here
faas
  .http(async (ctx: faas.HttpContext): Promise<faas.HttpContext> => {
    try {
      // retrieves all customers from the customers collection
      const customers = await documents()
        .collection<Customer>("customers")
        .query()
        .fetch();

      const customerResults = [];

      for (const customer of customers.documents) {
        customerResults.push(customer.content);
      }
      ctx.res.json(customerResults);
    } catch (e) {
      ctx.res.status = 500;
      ctx.res.body = new TextEncoder().encode("An unexpected error occurred");
    }

    return ctx;
  })
  .start();
```

### Reading an individual customer

The final function will read a customer using their id. To retrieve the id from the request path we will use a helper function called `path` which is located in the `common/path.ts` file. Let's update this function to retrieve the id from the customers path:

```typescript
// common/path.ts
import { Path } from "path-parser";

export const path = new Path("/customers/:id");
```

Note that we are utilizing an awesome library called <a href="https://www.npmjs.com/package/path-parser" target="_blank">path-parser</a> to extract the id from the path, there's no point reinventing the wheel.

Now update the read function to retrieve a customer.

```typescript
// functions/read.ts
import { faas, documents } from "@nitric/sdk";
import { Customer, path } from "../common";

// Start your function here
faas
  .http(async (ctx: faas.HttpContext): Promise<faas.HttpContext> => {
    // get params from path
    const { id } = path.test(ctx.req.path);

    if (!id) {
      ctx.res.body = new TextEncoder().encode("Invalid Request");
      ctx.res.status = 400;
    }

    try {
      console.log("getting doc id", id);
      const customer = await documents()
        .collection<Customer>("customers")
        .doc(id)
        .get();

      ctx.res.json(customer);
    } catch (e) {
      ctx.res.status = 500;
      ctx.res.body = new TextEncoder().encode("An unexpected error occurred");
    }

    return ctx;
  })
  .start();
```

## Defining our API

The Nitric framework takes full advantage of the [OpenAPI Specification](https://swagger.io/resources/open-api/) to define and deploy your APIs. APIs are typically defined in an `api.yaml` file, so let’s start defining:

```yaml
openapi: 3.0.0
info:
  version: 1.0.0
  title: Customer API
  description: Customer API
paths:
  /customers:
    get:
      operationId: customers-list
      x-nitric-target:
        name: list
        type: function
      description: Retrieve all customers
      responses:
        "200":
          description: Successful response
    post:
      operationId: customers-create
      x-nitric-target:
        name: create
        type: function
      description: Creates and persists new customers
      responses:
        "200":
          description: Successful response
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CustomersCreate"
  /customers/{customerId}:
    get:
      operationId: customers-read
      parameters:
        - in: path
          name: customerId
          schema:
            type: string
          required: true
      x-nitric-target:
        name: read
        type: function
      description: Retrieve an existing customer by its ID
      responses:
        "200":
          description: Successful response
components:
  schemas:
    CustomersCreate:
      type: object
      properties:
        name:
          type: string
        email:
          type: string
```

## Putting everything together

Let’s review our nitric stack before we start running it locally. Change the examples collection and the api reference to customers:

```yaml
name: customers
# Nitric functions
functions:
  create:
    handler: functions/create.ts
  read:
    handler: functions/read.ts
  list:
    handler: functions/list.ts
# Nitric collections
collections:
  customers: {}
# Nitric APIs
apis:
  customers: api.yaml
```

## Run and test your Stack

To run our stack locally, use the `nitric run` command. This will build and run your app locally using containers, exposing the API Gateway. Once complete, you should see an output like this:

```bash
✔ Building Services
✔ Creating docker network
✔ Running Storage Service
✔ Starting API Gateways
✔ Starting Entrypoints
 Function Port
 ──────── ─────
 create   54002
 list     54003
 read     54004
 Api     Port
 ─────── ─────
 customers 49152
```

Let’s test our API with cURL (to see testing using a postman like experience, view the video version), create customers by calling the API with different body data:

```bash
# POST: /customers
curl -H "Content-Type: application/json" -X POST -d '{"name":"David","email": "dave@example.com"}' http://localhost:49152/customers
```

You will see a successful output containing the generated customer id.

Now you can retrieve this customer from the API:

```bash
# GET: /customers/{customerId}
curl http://localhost:49152/customers/YOUR_CUSTOMER_ID
```

Try adding a few more customers and then list all of them:

```bash
# GET: /customers
curl http://localhost:49152/customers
```

Yay, see how easy that was?

## Deployment

Now that your customers API is ready, you can deploy your app to the cloud provider of your choice.

## Next Steps

Prefer Go, Python or Java? Why not try using a different language via our other Stack templates and SDKs.

Want to learn more? Visit our latest guides and [documentation](https://nitric.io/docs). if you have any questions, check out our [GitHub discussions](https://github.com/nitrictech/nitric/discussions) page 😃