Developing professional API with Azure Function Typescript + Boilerplate
Tags: azurefunctions, typescript, boilerplate, api
Published: 2023-03-01 16:18:41

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](https://github.com/safwanmasarik/Azure-Function-Boilerplate-Api#readme) file in the repository contains enough information for successfully running the project.

---
Let's get started.

## Folder structure
![Folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rawy2ilwnpa0c9ba5jjc.png)
- `.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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/irjvckupatpwzknihb90.png)
- Shared folder contains the core layers such as services (business logic), data layer, modelling and helpers.

## Framework and workflow

![shared-folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yzvyh8pb5womo1bam7jq.png)

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](https://www.npmjs.com/package/joiful)
Request query or body parameters input's will have constraints hence need to be validated. Here's how to do it.

Validation enforcement.
```ts
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
  };
}
```

Declaring validation constraints at class.
```ts
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;
}
```

### [json2typescript](https://www.npmjs.com/package/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.
```ts
import { JsonConvert } from "json2typescript";
let jsonConvert: JsonConvert = new JsonConvert();
const queryData: object[] = await data.getShip(params);
let modelledDbData: DbShip[] = jsonConvert.deserializeArray(queryData, DbShip);
```

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.
```ts
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;
}

```

Custom converter.
```ts
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);
    }
}
```

### Convert JSON data into Typescript class
- Utilize online converter such as [quicktype.io](https://app.quicktype.io/)

![quicktype](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6bwfh67karbvga1lqys7.png)

### View & validate JSON data
- Utilize online JSON data viewer and validator such as [JSONGrid](https://jsongrid.com/)

![jsongrid](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k66tz56xqduxfab6txqh.png)

## How to add new API endpoint
1. Make a copy of an api folder.
![api-01](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1b0l2ge1t7hgsw7qj7j.png)
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m8cm47dxp5wh1x1ly83w.png)
4. Update the `function.json`, `scriptFile` value to the new folder name.
![api-03](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t5g3ubcpcfdobpsfmgbl.png)
5. Now go `index.ts` and update the function name to the folder name.
![api-04](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2a0obbagykzio4puc857.png)
6. Your new API endpoint is ready. Press `F5` to run in `debug` mode and test it.
![api-05](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0uerftuwb2qx6jpmkuap.png)
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.