Upcoming Course: Code Your Own Business w/ React + GraphQL!
We're live-coding on Twitch! Join us!
CRUD with Azure Serverless Functions

CRUD with Azure Serverless Functions

Code Project

Serverless functions are lowering the barrier to backend entry for frontend and beginner developers. Personally, I have a huge relief that I don’t have to teach beginner developers DevOps topics — scaling, gateways, containers, hell no. I don’t even know these things myself. I love to write code that runs in browsers and other clients, but sometimes I just want a server to compute and store data for me.

In most use cases I have seen, they use Serverless as a tool for extending the capabilities of an existing system. Well, from a little research, I understand the reason for this. Our existing products, tools, solutions, platforms, etc =were written as monoliths — all their backend code tend to live in one codebase. It’s a huge time investment and super challenging to start tearing these monoliths into components. Therefore, the best option to gradually introduce serverless functions is to use it as an extension for the existing monolith.

Can Serverless Be More?

The situation I have just described has shaped the serverless ecosystem into that niche. Developers grow into the mental model that they can build a monolith for the main project, then use serverless to extend for new features. Unfortunately, this underestimates the power of the serverless architecture.

Serverless functions are as powerful as most monoliths.

That said, don’t be scared to start a new project or startup on its shoulders.

I made a demo to drive my points home. The examples consist of CRUD operations — Create, Read, Update and Delete. It writes and reads from table storage, and each CRUD operation lives as an independent function. The examples will also show you how to use TypeScript properly in serverless functions and also how to share code between independent functions without the need for a monolith.

Prerequisites

You will need the following setup to get started:

  1. An Azure account — you can get started for free.
  2. VS Code installed
  3. Node.js

Getting Started

Serverless functions can be created in different ways but in my opinion, the best way is using the VS Code extension. The extension allows you to create functions, configure env variables locally and in production, deploy and sync with your production, etc. This way, you never have to visit the deployment dashboard; everything happens right in your code editor. Cool, huh?

Essential Reading: Learn React from Scratch! (2019 Edition)

The extension relies on the Azure Function CLI tool. Install by running the following in your terminal:

npm install -g azure-functions-core-tools

Once you have done that, you can now head over and install the VS Code Functions extension.

We also need to store data for the CRUD ops. I kid you not; there is also a storage extension for creating storage. Please install it too so we can do everything right from VS Code.

We are set to start creating our functions.

  1. Create an empty directory on your computer and lunch the directory in VS Code.

  2. Start a new function creation process by clicking on the Azure extension logo:

  3. Sign in to the Azure account you created:

  4. Create a new Function Project by clicking the Function Project icon shown below:

  5. Select the name of the folder you created; the same folder you opened VS Code on

  1. Choose TypeScript as the language. You can also see the list of possible languages.
  2. Serverless functions are event-driven. Something needs to trigger them to start. We are building a web API, so HTTP Trigger is what we need.
  3. Give the function name. We are starting with the C in CRUD and naming the first function createTodo. Hit enter and choose Anonymous as the auth strategy.

This process will generate some starter code for you. For now, just focus on the createTodo folder which has an index.js file that holds the function’s logic.

Since we are building a CRUD ops app, we need four HTTP functions to handle each abbreviations. We will be creating them along the way but keep the following table in mind which shows the mappings:

Function Name CRUD Op
1 createTodo Create
2 listTodos Read
3 getTodo Read
4 updateTodo Update
5 deleteTodo Delete

Run the Functions

To see your scaffolded function in action, hit F5 to start the debugger.

Pay close attention to the log output to see what is happening behind the scene. Once the server is done starting, you should see the URL to each of your existing functions in the out. Feel free to follow the link

Data Service Class

There are a few challenges we would face if we dive right in into creating, reading, etc. These might not be challenges a beginner would have, but it’s a challenge we would have at scale. They are challenges that will also affect productivity and code communication. Here is a list of them:

  1. Functions use the async pattern. If you have a callback that has not resolved, the function would not wait for it and would return unless you called done in the callback. Async is a good thing, but it also means we have to structure our code in a way that it’s async-able.
  2. The SDK we will be using for data access is relies heavily on the node.js callback pattern. This makes issue one more obvious.
  3. Configs and connection code are redundant across API methods for CRUD operations.

These highlighted problems can easily be contained by abstracting into a data class. This class is written heavily with TypeScript will:

  1. Wrap the SDK API methods with Promise which makes them thenable, hence asyncable.
  2. Reuse logic for config and connection

Contracts with Interfaces

Create a folder lib in the project root and add a dataService.ts file. Import the SDK and define some types that we will make use of later:

import {
  createTableService,
  services,
  ServiceResponse,
  TableQuery
} from 'azure-storage';

type TableService = services.table.TableService;
type RequestOptions = services.table.TableService.TableEntityRequestOptions;

You need to install the SDK too:

npm install --save azure-storage

The custom types we have created serve as a reference to services.table.TableService and services.table.TableService.TableEntityRequestOptions. With this, we don’t have to type the verbose reference when we use the types.

Below the type definitions, define the interfaces to set some contract for the class, then create and export the class with no members for now:

interface IEntity {
  PartitionKey: string;
  RowKey: string;
  task: string | undefined;
}

interface IDataService {
  createTableIfNotExists(): Promise<string>;
  insertEntity(
    entity: IEntity,
    config: RequestOptions
  ): Promise<ServiceResponse>;
  updateEntity(entity: IEntity): Promise<ServiceResponse>;
  removeEntity(entity: IEntity): Promise<ServiceResponse>;
  getEntity(partitionKey: string, rowKey: string): Promise<ServiceResponse>;
  listEntities(query: TableQuery): Promise<ServiceResponse>;
}

class DataService implements IDataService {}

export default DataService;

Looking at the interfaces, you can already tell the requirements of DataService. The IEntity interface describes the input sent to the data store for storage. It describes a partition, the row key (aka ID) and the task which is the todo task.

IDataService describes all the members (all functions) that take in the entity and maybe config and returns a promise which when resolved, returns a ServiceResponse shape.

Initialize SDK with Constructor

When you create an instance of this class, we want to give you a name for the table which you can set locally in the class. We also want to initialize an instance of the SDK by calling createTableService(). In the DataClass:

private tableService: TableService;
constructor(private tableName: string) {
  this.tableName = tableName;
  this.tableService = createTableService();
}

Hey! Wondering where the connection string to the database lives? We will get to it right before we carry out another test.

Flesh Out the Class Methods

Let’s implement createTableIfNotExists() and insertEntity() methods inside the DataService:

createTableIfNotExists(): Promise<string> {
  return new Promise((resolve, reject) => {
    this.tableService.createTableIfNotExists(this.tableName, error => {
      if (!error) {
        resolve(this.tableName);
      } else {
        reject(error);
      }
    });
  });
}

insertEntity(
  entity: IEntity,
  config: RequestOptions
): Promise<ServiceResponse> {
  return new Promise(async (resolve, reject) => {
    await this.createTableIfNotExists();
    this.tableService.insertEntity(
      this.tableName,
      entity,
      config,
      (error, _, response) => {
        if (!error) {
          resolve(response);
        } else {
          reject(error);
        }
      }
    );
  });
}

createTableIfNotExists() is a method that will be used by every other method in this class. It helps them check if the table exist; if the table does not exists, it creates a table. insertEntity() wraps the SDK’s insertEntity and converts the callback based API to a Promise-based API.

The rest of the methods have this kind of behaviour:

updateEntity(entity: IEntity): Promise<ServiceResponse> {
  return new Promise(async (resolve, reject) => {
    await this.createTableIfNotExists();
    this.tableService.replaceEntity(
      this.tableName,
      entity,
      (error, _, response) => {
        if (!error) {
          resolve(response);
        } else {
          reject(error);
        }
      }
    );
  });
}

//...

Please copy the rest from dataService.ts on the repository.

Provision Database

Just like we created a function from VS code, we can also create a storage instance right from VS Code without switching context.

  1. Click the Azure Function extension
  2. Expand the Storage Tab
  3. Right click on the subscription and click Create Storage Account:

  1. Choose a name for the storage that has no space, contains only numbers and strings and is between 3 to 24 in length:

  2. Create a new resource with the same name, choose a data centre

  3. Copy the connection string by right-clicking on the storage:

  4. Open local.settings.json from the root of your project and update the content:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AZURE_STORAGE_CONNECTION_STRING": "<YOUR COPIED CONNECTION STRING>"
  }
}

The SDK will automatically look for AZURE_STORAGE_CONNECTION_STRING in your environment and set it up for you.

Creating Todos

Back to the fun part — let’s create an item. DataService class already did all the heavy lifting for us, all we just need to do is call the insertEntity method and pass it some arguments. In createTodo/index.ts paste the following:

import { AzureFunction, Context, HttpRequest } from '@azure/functions';
const shortid = require('shortid');
import DataService from '../lib/dataService';

const httpTrigger: AzureFunction = async function(
  context: Context,
  req: HttpRequest
): Promise<void> {
  const task = req.query.task || (req.body && req.body.task);
  const dataService = new DataService('todos');
  if (task) {
    const entity = {
      PartitionKey: 'Part1',
      RowKey: shortid.generate(),
      task
    };
    const res = await dataService.insertEntity(entity, { echoContent: true });
    context.res = {
      body: res
    };
  } else {
    context.res = {
      status: 400,
      body: 'Please pass a task on the query string or in the request body'
    };
  }
};
export default httpTrigger;

See how I am importing a strange library, shortid. This is just a simple library that when called, gives you a six-digit unique id. We want to use this id for our RowKey field.

The body of the function checks if I have passed a task through either the request body or the query string. If there is a task, it assembles the payload as entity and calls insertEntity on the instance of DataService to create a new task.

Notice we are awaiting the response from the call before sending a response with context.res.body. If task does not exist, we send a 400 with an error message. Before moving to read the todo items, let’s quickly test createTodo again.

Testing API with Postman Before we test, we have introduced some features that need to update our packages. Install the following:

npm install --save @types/node shortid

@types/node provides us with types for Node environment and shortid generates unique IDs for our rows. Now open Postman and paste the following link in the address bar:

http://localhost:7071/api/createTodo

Make a POST request with a request body as shown in the image below:

You should get a response in the response window.

Read List of Todos

Let’s create a function that will return all the existing todos we have created in our database. On the Azure Function extension toolbar, click on the new function icon (not the new function project icon):

Choose HTTPTrigger, name the function listTodos and paste the following code in the index.ts:

import { AzureFunction, Context } from '@azure/functions';
import { TableQuery } from 'azure-storage';
import DataService from '../lib/dataService';

const httpTrigger: AzureFunction = async function(context: Context): Promise<void> {
  const dataService = new DataService('todos')
  const query = new TableQuery().top(5).where('PartitionKey eq ?', 'Part1');

  const res = await dataService.listEntities(query)
  context.res = {
    body: res
  };
};

export default httpTrigger;

Run the function again with F5 and test the new listTodos url in Postman:

Reading, Updating and Deleting Todos.

Repeat the step for creating todos. It’s redundant so I did not want to bore you with the code, but the repository has all the code and they are structured just like the createTodo function:

Data Service Function
1 createTodo Data Service Lines Function
2 getTodo Data Service Lines Function
3. listTodo Data Service Lines Function
4. updateTodo Data Service Lines Function
5. deleteTodo Data Service Lines Function

Finally, Deploy the Functions

Deploying to Azure is the easiest part of the process. It’s the same experience as creating a storage instance. In the Function extension tab, click the arrow icon and follow the process:

The steps are 90% like creating a storage account and are super smooth.

You also need to publish the local env settings which hods the storage connection string. You can publish via VS Code after deploy by running: Command + Shift + P or CTRL + Shift + P. Next, search for Azure Functions: Upload Local Settings…:

Hit enter, choose the same subscription we have been using and chose the function project you want to publish the settings to.

With that, we have a production todo app!

Like this article? Follow @codebeast on Twitter