---
title: "Creating a Simple CRUD API with Zuplo and Firestore"
description: "Learn how to use Zuplo and Firebase to securely expose your Firestore data via REST API"
canonicalUrl: "https://zuplo.com/blog/2024/07/08/zuplo-plus-firebase-creating-a-simple-crud-api"
pageType: "blog"
date: "2024-07-08"
authors: "josh"
tags: "Firestore, Tutorial"
image: "https://zuplo.com/og?text=Creating%20a%20Simple%20CRUD%20API%20with%20Zuplo%20and%20Firestore"
---
When it comes to building apps quickly and scalably, Firebase has been a go-to
platform for many developers. Firebase makes it super easy to manage data,
authentication, serverless functions, and many other essential components for
modern apps. One downside is that Firebase lacks an easy way to expose data
within Firestore as a REST API. Luckily, by pairing it with Zuplo, we can create
a scalable and secure REST API in minutes that exposes our data hosted in
Firestore.

This tutorial will look at how to turn a simple Firestore collection into a REST
API. To do this, we will have a few prerequisites before you can follow along
step-by-step. These prerequisites include:

- Having an active Firebase account and access to the Firebase console
- An active Zuplo account
- Basic knowledge of REST APIs and JSON

Once you’ve handled these prerequisites, you can follow along below and create a
REST API that uses Firestore to handle CRUD operations. Let’s kick things off by
looking at setting up a new project in Zuplo.

<YouTubeVideo videoId="WUtt-A-zFk0" />

## Step 1: Setting Up Zuplo

Once you’ve signed into Zuplo, you must create a new project. If you’ve never
created a project before, when you log in, you’ll be prompted to create your
first project in the **New Project** modal.

If you already have an existing project, you’ll want to create a new one for
this by clicking the **New Project** button in the top-right of the **Projects**
screen. From here, the **New Project** modal will appear.

In the **New Project** modal, we will:

1. Name our project “firebase-todo”
2. Select the **Empty Project** type
3. Click **Create Project**

Here is what the filled-out modal will look like before clicking the **Create
Project** button.

![Create Zuplo project](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-52.png)

After clicking **Create Project**, Zuplo will kick off the process and spin up a
new API gateway for us that will be ready within a few moments. Generally, the
API gateway will be ready in under 10 seconds. With our API gateway spun up, our
next step is configuring Firestore to handle our CRUD operations.

## Step 2: Configure Firestore

Next, we will log into Firebase and navigate to the Firestore module. To do
this, from within the Firebase console, click **Build > Firestore Database** in
the left-side menu. This will take you to the **Cloud Firestore** main page.

From this page, we can manage our Firestore instance(s). If you have not created
a database yet, click the **Create database** button. For this example, choose
where you’d like the database to be hosted and set the security rules to
**production mode** so we will have a secure instance out of the box.

![Firestore setup](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-1.png)

Once your database is ready, or if you already have a database available, open
up the instance and click **Start collection** in the **Panel view.**

![Start collection](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-2.png)

In the **Start a collection** modal that appears, give this collection a
**Collection ID** of “todos” and click **Next**.

![Start collection modal](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-4.png)

Let’s add our first document to the collection in step two. To do this, we will:

1. Generate an **Auto-ID** for the collection by clicking the **Auto ID** button
   beside the **Document ID** field.
2. Populate the first **Field** input with “description”, setting the **Type**
   and **Value** to “string” and “Milk”.
3. Click **Add Field** and populate the **Field** input with “complete”, setting
   the **Type** and **Value** to “boolean” and “true”.
4. Click **Save** to save the collection to your Firestore instance.

Below is what the form will look like before clicking the **Save** button.

![configuring collection](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-5.png)

With our document saved, we can now see that our entry has persisted in the
Firestore instance.

![firestore welcome page](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-6.png)

With our Firestore instance up and running and some data in place, our next step
is to configure Firestore to accept the traffic from Zuplo to the database.

## Step 3: Configure Firebase Security

To configure the security for our API to access the Firestore instance, click on
**Project Overview** in the top-left corner of the screen in Firebase. You'll
need to create a web app in the **Zuplo-Todo** project (or however you’ve named
yours). If you have one already created, you can skip past the creation steps
documented below. If you need to create a web app, follow these steps.

First, click on the **Web App** icon in the **Project Overview** to begin the
creation flow.

![Firestore project overview](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-7.png)

On the next screen, fill in the **App nickname** field with “Zuplo Todo Web
App”, or another identifier of your choosing. Then, click **Register app**.

![Register firebase app](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-8.png)

In the second step, we will see instructions on how to integrate Firebase with
our web app. We won’t need to do this currently, so we will skip past it and
click **Continue to console**.

![Firebase sdk instructions](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-9.png)

At this point, our web app has been created, and we can proceed.

Using a newly created web app or an existing one, we will then select it from
the dropdown on the **Project Overview** screen. Available apps will be listed
at the top of the screen underneath the project name.

![new firebase app](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-10.png)

Click on the **Cog** to be brought into the **Project settings** screen.

On the **Project settings** screen, click the **Service Accounts** tab and then
click the **Manage Service Account permissions** link in the top-right of the
screen.

![Firebase project settings](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-11.png)

On the **Service accounts for project** screen, select the “Firebase Admin SDK
Service Agent” entry by clicking on the link in the **Email** field.

![Service accounts](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-12.png)

On the next screen, select the **Keys** tab. Under the **ADD KEY** dropdown,
choose **Create new key**.

![Creating a new key](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-13.png)

This will prompt you to create a new private key for the **firebase-adminsdk**
user. On the modal that appears, set the **Key type** as **JSON** and click
**CREATE**. From here, you’ll be prompted on where to save the .json file with
the credential. Once saved, you’ll see a confirmation on the screen.

![private key created](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-14.png)

This key will enable Zuplo to connect to our Firestore database. This will be
one of two pieces of authentication we will use to ensure our API and data are
secure. The **Service.json** will secure the Zuplo to Firestore connection and
we will use **API Key** authentication to make sure that clients connecting to
Zuplo are authenticated and authorized to use the API they are making the
request to.

![system diagram](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-15.png)

Our next step is to begin defining routes for the APIs themselves. For that, we
will hop back into Zuplo.

## Step 4: Creating The GET Endpoint

Back in Zuplo, we will open up our **firebase-todo** app. In the menu on the
left, select **routes.oas.json**.

The first endpoint we will create will be our **/todos** endpoint, which will
retrieve all of our todos within Firestore. To set this up, we will need to do
the following on the **Route Designer** screen:

1. Set the **Summary** as “Get all todos”
2. Set the **Path** as “/v1/todos”
3. Set the **Method** as “GET”
4. Set **CORS** as “Anything Goes”
5. In the **Request Handler** section, set the:
   1. **Handler** as “URL Rewrite”
   2. **Rewrite URL** as
      ```
      https://firestore.googleapis.com/v1/projects/${env.PROJECT_ID}/databases/(default)/documents:runQuery
      ```

### Adding The Environment Variables

Based on the value in the **Rewrite URL** field, we need to grab the
**env.PROJECT_ID** from an environment variable within Zuplo.

First, we need to get the **Project ID** for the project from Firebase. To grab
this value, go back to your Firebase project settings, and under **General**,
copy the value under **Project ID**. In my case, this value is “zuplo-todo”.

![getting project ID](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-16.png)

With this value on the clipboard, we will click on the **Settings** tab in
Zuplo, select **Environment Variables** from the left-side menu, and click **Add
Variable**.

![Adding environment variable to Zuplo](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-17.png)

In the modal that appears, do the following:

1. Set the **Name** field as “PROJECT_ID”.
2. Set the **Value** field as “zuplo-todo”.
3. Click **Save** to persist the newly created environment variable.

![Zuplo env var modal](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-21.png)

After clicking **Save**, you should see that the variable has been added to the
**Environment Variables** list.

![Saved Zuplo env var](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-22.png)

While in the **Environment Variables** screen, let’s quickly look at adding the
**service.json** file contents we created in Firebase earlier. To do this,
you’ll once again click on **Add Variable** and add the following to the form in
the modal:

1. Set the **Name** field to “SERVICE_ACCOUNT_JSON”.
2. Set the **Value** field to the contents of the **service.json** file we saved
   earlier from Firebase
   1. Note that the actual file name will look similar to
      “zuplo-todo-985b6bba61e5.json”
3. Check this variable off as **Secret** to encrypt the value and make it
   invisible after setting it.
4. Lastly, click **Save** to save the variable.

![Marking env var as secret](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-18.png)

Once saved, this variable should also appear in the **Environment Variables**
list, like so:

![two environment variables](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-19.png)

Now, let’s navigate back to the **routes.oas.json** file in Zuplo and click
**Save** in the bottom left corner of the screen.

![Zuplo routes](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-20.png)

This will then take our current configuration and deploy it to the gateway. At
this point, we can run a quick test to see if our newly created endpoint is up
and running. By clicking back into our “Get all todos” route from the **Route
Designer**, we can see that we can run a test directly through the Zuplo UI.

![finding the test button](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-23.png)

Click the **Test** button, and a simple test pane will appear. This will be
pre-populated with our endpoint details, and we can just click **Test** to fire
off a request.

After the test has run, we will see that the endpoint is returning to us a **403
Forbidden** with a “Missing or insufficient permissions” message being returned
in the response body.

![403 API response](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-24.png)

This means that our API call was successfully routed to Firebase. However,
Firebase has not allowed the request to retrieve data from our Firestore
instance due to insufficient permissions.

We actually have to make a few adjustments to make this request work. As is, it
is being sent over and blocked for a few reasons.

Firstly, the query is being set over as a **GET** request, but it needs to be a
**POST** for the request to Firebase to work. To fix this, we will add a
**Policy** to the request pipeline to modify the request into a **POST** request
and add a request body to the call.

### Creating and Adding In Policies

To create the policy, we will do the following:

1. In the left-side menu, on the **modules** folder, click the **+** button.
2. Name the new file “set-query-body.ts”
3. In the new file, add the following code:

```typescript
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";

export default async function policy(
  request: ZuploRequest,
  context: ZuploContext,
  options: never,
  policyName: string,
) {
  const query = {
    structuredQuery: {
      from: [
        {
          collectionId: "todos",
        },
      ],
    },
  };

  const nr = new ZuploRequest(request, {
    body: JSON.stringify(query),
    method: "POST",
  });

  return nr;
}
```

4. Click **Save** in the bottom left corner of the screen.

Our policy has been created but still needs to be applied to our **/todos**
endpoint. To add the policy, do the following:

1. Within the **routes.oas.json** file, click on the “Get all todos” endpoint in
   the **Route Designer** (if you don’t already have it up)
2. In the **Request Handling** pane, click on **Policies** to expand the section
   and click **Add Policy**.

![Adding the policy](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-25.png)

3. On the **Choose Policy** screen, search for “custom” and then select the
   **Custom Code Inbound** policy.

![custom code policy](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-26.png)

4. On the **Create Policy** screen, we will set up the configuration to point to
   the **set-query-body** module we created earlier. To do this, we will
   1. Set the **Name** field to “set-query-body”
   2. Set the **Configuration** as follows:

```json
{
  "export": "default",
  "module": "$import(./modules/set-query-body)"
}
```

    3. Click **OK** to save the policy. Once everything is filled in, this is how
      the configuration should look before saving it.

![configuring the policy](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-27.png)

5. After clicking **OK**, the policy should be added to the **Request**
   pipeline. It will also show up in the Zuplo UI.

![new policy appears](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-28.png)

6. Our last step is to click **Save** in the bottom left corner of the screen.
   This will push this new configuration out to our API gateway.

### Testing The Endpoint

To test this policy and ensure it works, we can debug it by sending another test
through Zuplo. To do this, click the **Test** button within the **Request
Handling** pane.

In the **Test Your API** modal, click **Test** and wait for the response to
return. Once the response has come back, you’ll see we are still getting the
**403 Forbidden** error. However, when we click on **Logs** and expand them, we
can see that Zuplo is correctly receiving the request and rewriting the URL,
including adding our environment variable for **PROJECT_ID**.

![testing the API again](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-29.png)

### Adding The Upstream Firebase Admin Auth Header

Our request still doesn’t work because of one last piece: adding the
**Authorization** header so that Zuplo can call the Firebase API as an
authenticated and authorized user.

To add in the necessary authorization header, we can do the following:

1. We will add another policy for the “Get all todos” endpoint by clicking **Add
   Policy** within the request pipeline.
2. In the **Choose Policy** modal, type “firebase” and then choose the
   **Upstream Firebase Admin Auth** entry.

![adding firebase auth policy](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-30.png)

3. In the plugin configuration that appears, you’ll see the default logic that
   will create a JWT from the value in our **SERVICE_ACCOUNT_JSON** environment
   variable that we added earlier.

![configuring firebase auth policy](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-31.png)

4. Click **Okay** to add the policy to the request.
5. Click **Save** in the bottom left corner to deploy the updated configuration
   to our gateway.

Our endpoint will now use two policies in the request pipeline. You can confirm
this in the Zuplo UI by ensuring both are visible.

![second policy appears](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-32.png)

### Testing The Updated Endpoint

Once again, let’s click the **Test** button to test our endpoint. After running
a test request, we should be able to see a 200 OK status returned, as well as
the contents of our todo that we added earlier in Firestore. Success!

![testing the endpoint with new policy](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-33.png)

At this point, we’ve created a simple **GET** API that connects to our Firestore
database and returns the values in a collection. Of course, the API is
completely public at this point, but we will cover more about how to secure it
in the next tutorial, Firebase Day 2.

### Formatting the API Response

As we can see, the response from Firestore is not necessarily the most efficient
or easy to use. This is why we will add a policy to the response pipeline that
will format it in a way that will make it a bit more standardized and in the
form developers expect.

First, we will need to create the policy by:

1. Click the **+** button beside **modules** in the left-side menu to add a new
   file.
2. Select **Outbound Policy**.
3. Name the new file “firestore-response-to-json.ts”.

Within the **firestore-response-to-json.ts** file, we will add the following
code to format the response:

```typescript
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";

type MyPolicyOptionsType = {
  documentId: string;
};

export default async function policy(
  response: Response,
  request: ZuploRequest,
  context: ZuploContext,
  options: MyPolicyOptionsType,
  policyName: string,
) {
  if (response.ok) {
    const data = await response.json();
    context.log.info(data);

    const documentId = options.documentId ?? "id";

    let transformed;

    if (!Array.isArray(data)) {
      transformed = transformDoc(data, documentId);
    } else {
      // Check if the data is an array and has documents or only contains readTime
      transformed =
        Array.isArray(data) && data.some((item) => item.document)
          ? transformFirestoreResults(data, documentId)
          : [];
    }

    return new Response(JSON.stringify(transformed), {
      status: response.status,
      statusText: response.statusText,
      headers: response.headers,
    });
  }

  return response;
}

function transformFirestoreResults(results: any[], documentId: string) {
  return results.map((result) => {
    const { document } = result;
    return transformDoc(document, documentId);
  });
}

function transformDoc(document: any, documentId: string) {
  const fields = document.fields;

  let transformedDoc = {
    [documentId]: document.name.substring(document.name.lastIndexOf("/") + 1),
  };

  for (let field in fields) {
    const value = fields[field];
    const valueType = Object.keys(value)[0];
    transformedDoc[field] = parseValue(value[valueType], valueType, documentId);
  }

  return transformedDoc;
}

function parseValue(value: any, valueType: string, documentId: string) {
  switch (valueType) {
    case "stringValue":
      return value;
    case "booleanValue":
      return value;
    case "integerValue":
      return parseInt(value, 10);
    case "doubleValue":
      return parseFloat(value);
    case "timestampValue":
      return new Date(value);
    case "mapValue":
      return transformFirestoreResults([value], documentId);
    case "arrayValue":
      return value.values.map((v) =>
        parseValue(v[Object.keys(v)[0]], Object.keys(v)[0], documentId),
      );
    case "nullValue":
      return null;
    default:
      return value;
  }
}
```

After the code is in place, click **Save** in the screen's bottom-left corner.

With the policy created, we need to add the outbound policy to the response
pipeline. let’s do the following to add it:

1. In Zuplo, go back to our endpoint in **routes.oas.json**, and in the
   **Request Handling** pane, click on **Add Policy** (similar to what we did
   for adding the policy to our request).
2. In the **Choose Policy** modal, search for “custom” and select the **Custom
   Code Outbound** policy.

![Adding outbound policy](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-34.png)

3. In the **Create Policy** modal, set the policy **Name** as
   “firestore-to-json”
4. under **Configuration**, add the following code that will include our module
   and supply an option for **documentId** (the documentId value returned for
   the Firestore document).

```json
{
  "export": "default",
  "module": "$import(./modules/firestore-response-to-json)",
  "options": {
    "documentId": "id"
  }
}
```

    With the configuration plugged in, our screen should now look like this:

![configuring outbound policy](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-35.png)

5. From here, we will click **OK**, then click **Save** in the screen's
   bottom-left corner to save the updated endpoint and cascade out the new
   configuration to the gateway.

### Testing The Updated Endpoint

We are now ready to test the endpoint again using the **Test** button. This
time, when sending a request, we should get a response that is a bit more
readable after routing through our outbound policy that we just added to
transform the JSON response from Firestore. The result should look like this:

![testing outbound policy](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-36.png)

Now that we have built one API endpoint let's build the rest of our CRUD
endpoints, starting with adding a new to-do item.

## Step 5: Creating The POST Endpoint

For our **Create todo** endpoint, we will go back into **routes.oas.json** and,
from the **Route Designer**, do the following:

6. Click the **Add Route** button
7. Fill in the **Summary** value as “Create todo”
8. Add the **Path** as “/v1/todos”
9. Change the **Method** dropdown from “GET” to “POST”
10. Set **CORS** as “Anything Goes” to enable CORS.

With this set, our fields in the UI should look like this:

![New endpoint](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-37.png)

### Adding In Policies

Next, we will add our **Policies**. Clicking on the **Policies** section on the
screen, we will:

1. Under **Request**, click **Add Policy**.
2. On the **Choose Policy** modal, find and click on the **Upstream Firebase
   Admin Auth** policy (the same one we added on the GET route created earlier).
3. On the **Create Policy** modal, leave the settings as they are and click the
   **OK** button to add the policy to our API.

Then, under the **Request Handler** section, we will select the **Handler** as
“URL Rewrite” and set the **Rewrite URL** as:

```
https://firestore.googleapis.com/v1/projects/${env.PROJECT_ID}/databases/(default)/documents/todos
```

### Formatting the API Request

Lastly, we want to ensure the endpoint receives a sensibly formatted JSON body.
With the way Firestore works, it’s not necessarily intuitive from a REST
perspective for users trying to add a todo to the list. For this, we will create
a new inbound policy that will transform our incoming JSON into the format
Firestore expects.

To create the policy, under the **modules** folder in the left-side menu, we
will:

1. Click the **+** button to add a new **Inbound Policy**.
2. Name the new file “json-to-firestore-request.ts”.
3. Open up the newly created file in the UI by clicking on it.

We will then add the following code to our policy:

```typescript
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";

export default async function (request: ZuploRequest, context: ZuploContext) {
  const requestBody = await request.json();

  // Transform the incoming JSON body into Firestore format
  const firestoreDocument = {
    fields: {},
  };

  for (const [key, value] of Object.entries(requestBody)) {
    let firestoreValue;

    switch (typeof value) {
      case "string":
        firestoreValue = { stringValue: value };
        break;
      case "boolean":
        firestoreValue = { booleanValue: value };
        break;
      case "number":
        if (Number.isInteger(value)) {
          firestoreValue = { integerValue: value.toString() };
        } else {
          firestoreValue = { doubleValue: value };
        }
        break;
      default:
        throw new Error(`Unsupported data type for key ${key}`);
    }

    firestoreDocument.fields[key] = firestoreValue;
  }

  const nr = new ZuploRequest(request, {
    body: JSON.stringify(firestoreDocument),
  });

  nr.headers.set("content-type", "application/json");

  // Continue processing the request
  return nr;
}
```

In this code, we are parsing the body received by our endpoint and converting it
into the format expected by Firestore. This includes taking the TypeScript type
and converting it into the equivalent type that Firestore supports. The policy
will return a new request that is correctly formatted and can be sent to
Firestore to add the todo from the request body.

Once the code is in place, we will click **Save** in the bottom left corner to
persist it in Zuplo and push it out to our gateway.

With our new policy created, our next step is to add our new
**json-to-firestore-request** policy to our request pipeline. For this, back in
the **routes.oas.json** file, we will do the following:

1. Expand the **Policies** section and click **Add Policy** under **Response**.
2. On the **Choose Policy** modal, select **Custom Code Inbound**.
3. On the **Create Policy** modal, set the **Name** field as
   “json-to-firestore“, and make the following changes in the **Configuration**
   pane:
   1. Change the “module” value in the code to
      “$import(./modules/json-to-firestore-request”.
   2. Delete the “options” object, as it is not needed for this policy.
4. Click **OK**.

This is what the **Create Policy** screen should look like before you save it:

![Creating fourth policy](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-38.png)

With both policies in place, our endpoint configuration is complete and should
look like this:
![fourth policy appears](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-39.png)

Click **Save** in the screen's bottom left corner to push the new endpoint out
to the gateway.

### Testing The Endpoint

Now, we can proceed with testing it by clicking the **Test** button beside the
**Path** field on the **Route Designer** pane.

On the **Test Your API** modal, we will add a request body, send the request,
and, if all goes well, hopefully, receive a 200 OK response. Below is an example
of a request that you can test with this endpoint, as well as what the screen
should display when you receive a successful response.

![testing fourth policy](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-40.png)

If you want to do a secondary test to see the latest entry being retrieved from
the **GET** endpoint we created earlier, we can:

1. Click on the endpoint in the **Route Designer.**
2. Open the **Test Your API** modal by clicking the **Test** button beside the
   **Path** field.
3. Click **Test** in the **Test Your API** modal.

After the response has come back, we should see our latest entry added to the
returned array of todos.

![inspecting response body](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-41.png)

### Formatting The API Response

Our final step here is to make the response from the new **POST** endpoint look
more familiar to users of the REST API. We will reuse our previous
**firestore-to-json** policy and add it as an outbound policy to our response
pipeline. To add the policy, do the following:

1. Under **Policies**, click **Add Policy** under **Response**.
2. On the **Choose Policy** screen, select **firestore-to-json** under
   **Existing Policies**.
3. Click **Save** in the bottom left corner to deploy the latest configuration
   to the gateway.

This will add the policy to the request pipeline and transform our oddly
formatted Firestore response into a nice, pretty JSON response.

### Test The Updated Endpoint

Let’s run another test, following the same steps as before. Only this time will
we add an item for “Crackers”. After you’ve run the test, here’s what our output
should look like:

![formatted response](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-42.png)

We now have a nicely formatted response that feels more like a traditional REST
API. We now have two endpoints: one to create todos and another to retrieve
them. Our next step is to create an update endpoint to update entries within our
todos list.

## Step 6: Creating The PATCH Endpoint

For our **Update todo** endpoint, we will go back into **routes.oas.json** and,
from the **Route Designer**, do the following:

1. Click the **Add Route** button
2. Fill in the **Summary** value as “Update todo”
3. Add the **Path** as “/v1/todos/&#123;todoId}”
4. Change the **Method** dropdown from “GET” to “PATCH”
5. Set **CORS** as “Anything Goes” to enable CORS.

You’ll notice we added a URI parameter to this request. The route must update a
specific item based on its ID. By adding the &#123;todoId}, we are letting Zuplo
know that this is where that particular parameter will be when processing the
request. With this set, our fields in the UI should look like this:

![creating patch endpoint](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-43.png)

### Add In Policies

Next, we will add our **Policies**. Clicking on the **Policies** section on the
screen, we will:

1. Under **Request**, click **Add Policy**.
2. On the **Choose Policy** modal, find and click on the **Upstream Firebase
   Admin Auth** policy (the same one we added on the GET route created earlier).
3. On the **Create Policy** modal, leave the settings as they are and click the
   **OK** button to add the policy to our API.
4. Next, click **Add Policy** again under **Request**.
5. Under **Existing Policies**, select **json-to-firestore**.
6. Finally, under **Response**, click **Add Policy** and select
   **firestore-to-json** under **Existing Policies**.

This configuration will allow us to send upstream requests to Firestore and send
and receive nicely formatted JSON requests and responses as we have done with
the other endpoints.

Then, under the **Request Handler** section, we will once again select the
**Handler** as “URL Rewrite” and set the **Rewrite URL** as:

```
https://firestore.googleapis.com/v1/projects/${env.PROJECT_ID}/databases/(default)/documents/todos/${params.todoId}?updateMask.fieldPaths=complete&updateMask.fieldPaths=description&updateMask.fieldPaths=complete
```

You’ll notice that this URL has a bit more added to it compared to the one we
did for our **POST** endpoint.

Firstly, you’ll see that our URI parameter from the **Path** is being pushed
into our **Rewrite URL** using **$&#123;params.todoId}**. This will allow us to
pass through that variable and ensure that the correct entry is being updated in
Firestore.

Secondly, you’ll see that we have also added a query parameter to the **Rewrite
URL**. This is because we want to make this into a really clean interface for
our API users, and by using **updateMask** in the URL as a query parameter, we
can have it so that it will only update the fields that are passed and not the
entire object. Suppose we only update the **description** field and don’t send
the others. In that case, Firebase will only update that one field instead of
overwriting the entire object (potentially with null values, blowing away our
previous values). In the URL, you will see that we’ve added this for both
**description** and **complete,** as those are the two fields we care about.

With the **Policies** and **URL Rewrite** dialed in, our configuration should
now look like this in the **Route Designer**:

![patch endpoint configuration](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-44.png)

Click **Save** in the screen's bottom left corner to push the new endpoint out
to the gateway.

### Testing The Endpoint

We will once again test out our new endpoint by clicking the **Test** button
beside the **Path** field on the **Route Designer** pane.

Since we will be updating an entry, we must grab the document ID from Firestore
for the entry we want to change. We will change “Milk” to “Almond Milk” in our
example. To grab the ID for our Milk entry, you can either call the **GET**
endpoint and copy the ID value or grab it directly from Firestore. If you grab
it from the GET endpoint in Zuplo, this is where that value can be found:

![testing patch endpoint](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-45.png)

On the **Test Your API** modal, we will

1. add the ID for “Milk” as a URI parameter, overwriting the placeholder
   **&#123;todoId}**.
2. In the request body, we will add a JSON object with a new description for our
   item.
3. Click **Test** to send the request.

We will receive a **200 OK** response if all goes well. Below is an example of a
request you can test with this endpoint (you’ll need to switch the URI
parameter), as well as what the screen should display when you receive a
successful response.

![200 OK response](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-46.png)

We can also confirm back in **Firestore** that the update was successful. By
going to that specific document, we should see that the value is now “Almond
Milk”.

![Checking in firestore](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-47.png)

With 3 endpoints created, we want to round out our CRUD APIs by adding a
**DELETE** endpoint. Let’s tackle that next!

## Step 7: Creating The DELETE Endpoint

For our **Delete todo** endpoint, we will go back into **routes.oas.json** and,
from the **Route Designer**, do the following:

1. Click the **Add Route** button
2. Fill in the **Summary** value as “Delete todo”
3. Add the **Path** as “/v1/todos/&#123;todoId}”
4. Change the **Method** dropdown from “GET” to “DELETE”

Similar to our **PATCH** endpoint, we added a URI parameter to this **Path**.
This is because the route must delete a specific item based on its ID. With this
set, our fields in the UI should look like this:

![Delete endpoint](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-48.png)

### Adding In Policies

Next, we will add our **Policies**. Clicking on the **Policies** section on the
screen, we will:

1. Under **Request**, click **Add Policy**.
2. On the **Choose Policy** modal, find and click on the **Upstream Firebase
   Admin Auth** policy (the same one we added on the GET route created earlier).
3. On the **Create Policy** modal, leave the settings as they are and click the
   **OK** button to add the policy to our API.

Here, you’ll see we only needed to add the **Upstream Firebase Admin Auth**
policy since we have no request or response JSON to deal with.

Then, under the **Request Handler** section, we will once again select the
**Handler** as “URL Rewrite” and set the **Rewrite URL** as:

```
https://firestore.googleapis.com/v1/projects/${env.PROJECT_ID}/databases/(default)/documents/todos/${params.todoId}
```

Again, like the **PATCH** endpoint, we’ve added the URI parameter to the
endpoint’s **Path** and injected it into our **Rewrite URL** so that Firestore
knows which entry to delete.

With the **Policies** and **URL Rewrite** dialed in, our configuration should
now look like this in the **Route Designer**:

![Configured deletion endpoint](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-49.png)

Click **Save** in the screen's bottom left corner to push the new endpoint out
to the gateway.

### Testing The Endpoint

We will once again test out our new endpoint by clicking the **Test** button
beside the **Path** field on the **Route Designer** pane.

Since our API will delete data, we must grab the document ID from Firestore for
the entry we want to delete. In our test, we will delete the “Almond Milk” entry
we updated earlier. To grab the ID for our Almond Milk entry, call the **GET**
endpoint and copy the ID value or grab it directly from Firestore.

On the **Test Your API** modal, we will

4. add the ID for “Almond Milk” as a URI parameter, overwriting the placeholder
   **&#123;todoId}**.
5. In the request body, we will add a JSON object with a new description for our
   item.
6. Click **Test** to send the request.

Hopefully, we will receive a **200 OK** response if all goes well. Below is an
example of a request that you can test with this endpoint (you’ll need to switch
the URI parameter), as well as what the screen should display when you receive a
successful response, including an empty response body.

![Testing delete endpoint](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-50.png)

We can also confirm in **Firestore** that the delete request was successful. In
**Firestore**, we should see that an “Almond Milk” entry no longer exists and
that only 2 items remain, one for “Bread” and one for “Crackers”.

![Checking against firestore](/media/posts/2024-07-08-zuplo-plus-firebase-creating-a-simple-crud-api/image-51.png)

## Conclusion

With that, we’ve done it! We’ve created our CRUD APIs by using Zuplo to create a
sleek REST interface with Firestore as the backend. In minutes we’ve created a
fully functional and scalable REST API using Firebase Firestore and Zuplo.

In the next parts of this series we will build on these APIs and add the
following functionalities:

- Day 2: Add API key authentication.

- Day 3: Add validation for requests.

- Day 4: Generate developer documentation.

- Day 5: Monetize the API and add AI features.

Keep following for more detailed step-by-step tutorials and enhancements
throughout Firebase week!