Serverless API

Build your own serverless API

Introduction

In this tutorial, you will learn how to build a serverless API using Morty. You will create a simple API that returns a list of users. You will also learn how to deploy your API to the cloud.

Prerequisites

Create a new project

To create a new project, run the following command:

morty function init -r node-19 serverless-api

This will create a new project in the serverless-api directory. The -r flag specifies the runtime to use. In this case, we will use the node-19 runtime.

Create our function

Update the serverless-api/handler.js file with the following content:

First, we will create an in-memory database to store our users. We will use an array of users for this example. Then, we will create a set of functions to interact with our database. We will create functions to get all users, get a user by id, create a new user, update a user and delete a user. Finally, we will export our functions as a module. This will allow us to use our functions in our function handler.

Database

For this example, we will use a simple array of users as our database. Of course, this is not a good idea for a production application but it will be enough for this example. Database will be reinitialized every time the function is instantiated. This is because the function is stateless and will be destroyed after some time of inactivity.

// In-memory database
// Only for demo purposes, do not use in production
let users = [
  { id: 1, name: "John Doe" },
  { id: 2, name: "Alicia Lacoupe" },
  { id: 3, name: "Josh Dupont" }
];

Functions

To get all users, we will create a function that returns the users array.

const getAllUsers = () => {
  return users;
};

To get a user by id, we will create a function that returns the user with the specified id.

const getUserById = (id) => {
  return users.find((user) => user.id == id);
};

To create a new user, we will create a function that adds the user to the users array and returns the newly created user.

const createUser = (user) => {
  users.push(user);
  return user;
};

Same logic can be used to update a user. We will create a function that updates the user with the specified id and returns the updated user.

const updateUser = (id, user) => {
const index = users.findIndex((user) => user.id == id);
users[index] = { ...users[index], ...user };
return users[index];
};

Finally, to delete a user, we will create a function that removes the user with the specified id from the users array and returns the updated array.

const deleteUser = (id) => {
users = users.filter((user) => user.id != id);
return users;
};

Finally, we will export our functions as a module. This will allow us to use our functions in our function handler.

// Export our functions as a module
exports.handler = async function (req, res) {
  // Get the request method, query parameters and body (req is an express request object)
  const { method, query, body } = req;
 
  switch (method) {
    case "GET":
      const response = query.userId ? getUserById(query.userId) : getAllUsers();
      return res.status(200).send(JSON.stringify(response));
    case "POST":
      if (!body || body === "" || body === "{}") {
        return res.status(400).send("Missing body");
      }
      const user = createUser(JSON.parse(body));
      return res.status(200).send(JSON.stringify(user));
    case "PUT":
      if (!query.userId) {
        return res.status(400).send("Missing userId");
      }
      if (!body) {
        return res.status(400).send("Missing body");
      }
      const updatedUser = updateUser(query.userId, body);
      return res.status(200).send(JSON.stringify(updatedUser));
    case "DELETE":
      if (!query.userId) {
        return res.status(400).send("Missing userId");
      }
      const isDeleted = deleteUser(query.userId);
      return res.status(200).send(JSON.stringify(isDeleted));
    default:
      return res.status(405).end(`Method ${method} Not Allowed`);
  }
};

Final code

Here is the final code for our function:

// In-memory database
// Only for demo purposes, do not use in production
let users = [
  { id: 1, name: "John Doe" },
  { id: 2, name: "Alicia Lacoupe" },
  { id: 3, name: "Josh Dupont" }
];
 
const getAllUsers = () => {
  return users;
};
 
const getUserById = (id) => {
  return users.find((user) => user.id == id);
};
 
const createUser = (user) => {
  users.push(user);
  return user;
};
 
const updateUser = (id, user) => {
  const index = users.findIndex((user) => user.id == id);
  users[index] = { ...users[index], ...user };
  return users[index];
};
 
const deleteUser = (id) => {
  users = users.filter((user) => user.id != id);
  return users;
};
 
// Export our functions as a module
exports.handler = async function (req, res) {
  // Get the request method, query parameters and body (req is an express request object)
  const { method, query, body } = req;
 
  switch (method) {
    case "GET":
      const response = query.userId ? getUserById(query.userId) : getAllUsers();
      return res.status(200).send(JSON.stringify(response));
    case "POST":
      if (!body || body === "" || body === "{}") {
        return res.status(400).send("Missing body");
      }
      const user = createUser(JSON.parse(body));
      return res.status(200).send(JSON.stringify(user));
    case "PUT":
      if (!query.userId) {
        return res.status(400).send("Missing userId");
      }
      if (!body) {
        return res.status(400).send("Missing body");
      }
      const updatedUser = updateUser(query.userId, body);
      return res.status(200).send(JSON.stringify(updatedUser));
    case "DELETE":
      if (!query.userId) {
        return res.status(400).send("Missing userId");
      }
      const isDeleted = deleteUser(query.userId);
      return res.status(200).send(JSON.stringify(isDeleted));
    default:
      return res.status(405).end(`Method ${method} Not Allowed`);
  }
};

Deploy our function

To deploy our function, run the following command:

morty function build ./serverless-api

Test our function

To test our function, run the following command:

Get all users

morty function invoke serverless-api -X GET

Response :

[
  {
    "id": 1,
    "name": "John Doe"
  },
  {
    "id": 2,
    "name": "Alicia Lacoupe"
  },
  {
    "id": 3,
    "name": "Josh Dupont"
  }
]

Create a new user

morty function invoke serverless-api -X POST -d "{ id: 4, name: 'Jack Sparrow' }" -H "Content-Type: application/json"

Response :

{
  "id":4,
  "name":"Jack Sparrow"
}

Update a user

morty function invoke serverless-api1 -X PUT -p userId=2 -d "{ name: 'Serge Blanco' }" -H "Content-Type: application/json"

Response :

{
  "id":2,
  "name":"Serge Blanco"
}

Get a user by id

morty function invoke serverless-api1 -X GET -p userId=2

Response :

{
  "id":2,
  "name":"Serge Blanco"
}

Delete a user

morty function invoke serverless-api1 -X DELETE -p userId=2

Response :

[
  {
    "id": 1,
    "name": "John Doe"
  },
  {
    "id": 3,
    "name": "Josh Dupont"
  }
]