DEV Community

Cover image for Developing professional API with Azure Function Typescript + Boilerplate
safwanmasarik
safwanmasarik

Posted on

Developing professional API with Azure Function Typescript + Boilerplate

The article here is aimed to give details about framework, workflow and libraries implemented for github repository Azure Function Node.js Typescript Starter and Boilerplate at https://github.com/safwanmasarik/Azure-Function-Boilerplate-Api.

Highlights of this boilerplate:

  • ⚡️ Azure Function Typescript support
  • ♨️ Hot reload capability — auto compile on save and server restart
  • 🃏 Jest — Configured for unit testing + mocking db response
  • ✨ Linq package — an alternative to lodash, Typescript support for enumerating collections
  • 📏 Mssql package — support for local database
  • 💨 Json2Typescript package — Modelling json object to Typescript object
  • 🤣 Joiful package — Joi for Typescript, validate api parameters with class & @decorators.
  • 📈 Typescript project diagnostics enabled — quickly catch error by compiling on background and displaying error in problems bar.
  • 📏 Auto format on save
  • 🌈 Bracket pair colorizer enabled
  • 🤖 Visual Studio code full support and intellisense.
  • 🦠 Microservice architecture — api & database separate repository, no ORM.

Back-End API Technology

  • Azure Functions
  • Node.js
  • TypeScript
  • Microsoft SQL Server

The README file in the repository contains enough information for successfully running the project.


Let's get started.

Folder structure

Folder structure

  • .vscode contains extension recommendations, debug launch settings, build tasks and default IDE settings. Note that IDE settings in workspace.code-workspace will override .vscode/settings.
  • Default folder structure for Azure Function Node.js is maintained, so the API functions must reside on the root directory. Naming for the API functions is prefixed with az_* to make all the API functions sorted on top.
  • Examples of API naming based on the function type such as http,timerTrigger or queueTrigger. api-naming-example
  • Shared folder contains the core layers such as services (business logic), data layer, modelling and helpers.

Framework and workflow

shared-folder

  1. The adopted framework follows a domain-driven approach, with separate layers for API, service, and data.
  2. First entry point is the API layer which will call the service layer and receive the api response to be returned.
  3. The service layer will perform business logic such as calculations and data formatting. It also handles calling the data layer.
  4. The data layer is only responsible to retrieve the data. Data retrieval from database or 3rd party API should be conducted here.
  5. Most JSON object will be converted to Typescript class object and the classes, interface and enums are stored in models folder.
  6. All classes and functions that are deemed as helpers is stored in helpers folder.
  7. For unit testing, only service layer and necessary helpers will be tested. Unit testing should not do an actual call to the data layer therefore data responses must be mocked.
  8. Data layer testing will be covered by integration test which will make actual api call and actual data retrieval from source. Example of integration test with postman collection for CRUDE operation is available in folder postman/AzFuncBoilerplate-IntegrationTest.postman_collection.json. In this post, we'll not cover for this content.

Libraries & Tooling support

Joiful

Request query or body parameters input's will have constraints hence need to be validated. Here's how to do it.

Validation enforcement.

import * as jf from 'joiful';

// Get the request body
const params = new ReqCreateUpdateDeleteShip();
params.ship_name = req?.body?.ship_name ?? null;
params.ship_code = req?.body?.ship_code ?? null;

// Validate request body
const joiValidation = jf.validate(params);

if (joiValidation.error) {
  return {
    is_valid: false,
    message: joiValidation.error.message,
    data: null
  };
}
Enter fullscreen mode Exit fullscreen mode

Declaring validation constraints at class.

import 'reflect-metadata';
import * as jf from 'joiful';

export class ReqCreateUpdateDeleteShip {

  @jf.number().optional().allow(null)
  ship_id: number;

  @jf.boolean().optional()
  is_permanent_delete: boolean;

  @jf.string().required()
  ship_name: string;

  @jf.string()
    .regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "kebab case ('kebab-case', 'going-12-merry', 'jackson')")
    .required()
  ship_code: string;

  @jf.string().email()
  email: string;

  @jf.string().creditCard()
  credit_card_number: string;
}
Enter fullscreen mode Exit fullscreen mode

json2typescript

  • Responses from the data layer comes in the form of JSON object. json2typescript maps JSON objects to an instance of a TypeScript class aka Deserialization.
  • Service layer will benefit having data layer response modelled as it provides consistent class and property for business logic and formatting.
  • Intellisense support.
  • Easy to change -> If data response property names changes, class would not need to be changed because the library provide JsonProperty decorator which allows easy adjustment of JSON data mapping.

Deserialization.

import { JsonConvert } from "json2typescript";
let jsonConvert: JsonConvert = new JsonConvert();
const queryData: object[] = await data.getShip(params);
let modelledDbData: DbShip[] = jsonConvert.deserializeArray(queryData, DbShip);
Enter fullscreen mode Exit fullscreen mode

Class and property decorators.

  • For class properties to be visible to the mapper they must be initialized, otherwise they are ignored.
  • They can be initialized using any (valid) value, undefined or null.
import { JsonObject, JsonProperty } from "json2typescript";
import { DateConverter } from "../../helpers/json-converter";

@JsonObject("DbShip")
export class DbShip {
  @JsonProperty("id", Number, true)
  ship_id: number = null;

  @JsonProperty("name", String, true)
  ship_name: string = null;

  @JsonProperty("code", String, true)
  ship_code: string = null;

  @JsonProperty("is_active", Boolean, true)
  is_active: boolean = null;

  @JsonProperty("updated_date", DateConverter, true)
  updated_date: Date = null;
}

Enter fullscreen mode Exit fullscreen mode

Custom converter.

import { JsonConvert, JsonConverter, JsonCustomConvert } from "json2typescript";

let jsonConvert: JsonConvert = new JsonConvert();

@JsonConverter
export class DateConverter implements JsonCustomConvert<Date> {
    serialize(date: Date): any {
        return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
    }

    deserialize(date: any): Date {
        return new Date(date);
    }
}
Enter fullscreen mode Exit fullscreen mode

Convert JSON data into Typescript class

quicktype

View & validate JSON data

  • Utilize online JSON data viewer and validator such as JSONGrid

jsongrid

How to add new API endpoint

  1. Make a copy of an api folder. api-01
  2. To make life easy delete the __test__ folder inside the new folder.
  3. Rename the new folder and copy the folder name. api-02
  4. Update the function.json, scriptFile value to the new folder name. api-03
  5. Now go index.ts and update the function name to the folder name. api-04
  6. Your new API endpoint is ready. Press F5 to run in debug mode and test it. api-05
  7. While in debug mode you can adjust the service layer that you want your api layer to call. The changes is watched and will auto-compile on save and server restart will happen automatically.
  8. Please note that changing environments such that changing values in local.settings.json will require you to restart the server manually.

Final Thoughts

Thank you for taking the time to read my first public technical article. I am grateful to the wonderful developer community out there for nurturing me, and now, it's my turn to give back. I've enjoyed learning and sharing my experiences, and I hope you find them helpful.

If you liked this article and you think this will help other's out there, feel free to share it. Comment if you feel something can be improved or added.

Top comments (2)

Collapse
 
physio profile image
Gallo Mauro

Thank you Safwan, very interesting!

Collapse
 
safwanmasarik profile image
safwanmasarik

Glad you like it. Appreciate the support, thanks 😊.