Starting from this chapter?

Clone the application repo and check out the getting-started branch:

git clone git@github.com:auth0-blog/wab-ts-express-api.git \
express-ts-api \
--branch getting-started

Make the project folder your current directory:

cd express-ts-api

Then, install the project dependencies:

npm i

Finally, create a .env hidden file:

touch .env

Populate .env with this:

PORT=7000

Before creating any controllers and services, let's define the structure of the data this application handles. A menu item has the following properties:

  • id: (number) Unique identifier for the item record
  • name: (string) Name of the item
  • price: (number) Price of the item in dollars
  • description: (string) Description of the item
  • image: (string) URL pointing to the item's image

WHATBYTE Dashboard full menu item

Since you are using TypeScript to build your API, you have different options to define and enforce the structure of objects during development. You can use either classes or interfaces to define custom types. For the menu items, you can use interfaces as they are not part of the compiled JavaScript bundle, keeping it smaller, and your application won't need instances of a menu item.

To keep your project organized, create an items directory under the src directory to store all files related to menu items:

mkdir src/items

Model API Resources with TypeScript Interfaces

To start modeling your data, create a file to hold the definition of an Item type:

touch src/items/item.interface.ts

Populate src/items/item.interface.ts with the following definition:

// src/items/item.interface.ts

export interface Item {
  id: number;
  name: string;
  price: number;
  description: string;
  image: string;
}

Next, create a file to hold the definition of an Items type, a bundle of elements of type Item:

touch src/items/items.interface.ts

Populate src/items/items.interface.ts as follows:

// src/items/items.interface.ts
import { Item } from "./item.interface";
export interface Items {
  [key: number]: Item;
}

Create a Data Service to Manipulate API Resources

A service lets you encapsulate related business logic that you can share across multiple projects. As such, your application can use a service to access and manipulate records from your store.

Create a file to define a service module:

touch src/items/items.service.ts

Populate the src/items/items.service.ts file with the following template:

/**
 * Data Model Interfaces
 */


/**
 * In-Memory Store
 */

/**
 * Service Methods
 */

Now, under the Data Model Interfaces section, import the interfaces you created earlier:

/**
 * Data Model Interfaces
 */

import { Item } from "./item.interface";
import { Items } from "./items.interface";

To keep this tutorial simpler, you are not using an external database to store records. Instead, under the In-Memory Store section, you create a simple in-memory store represented as an object:

/**
 * In-Memory Store
 */

const items: Items = {
  1: {
    id: 1,
    name: "Burger",
    price: 5.99,
    description: "Tasty",
    image: "https://cdn.auth0.com/blog/whatabyte/burger-sm.png"
  },
  2: {
    id: 2,
    name: "Pizza",
    price: 2.99,
    description: "Cheesy",
    image: "https://cdn.auth0.com/blog/whatabyte/pizza-sm.png"
  },
  3: {
    id: 3,
    name: "Tea",
    price: 1.99,
    description: "Informative",
    image: "https://cdn.auth0.com/blog/whatabyte/tea-sm.png"
  }
};

It's important to note that anytime that you reset the server, the in-memory store is wiped. However, since you are using webpack's Hot-Module Replacement, that only happens when you make changes to the service module file.

Next, create methods to perform read and write operations on the items store. Start by creating two methods under the Service Methods section to find store elements:

/**
 * Service Methods
 */

export const findAll = async (): Promise<Items> => {
  return items;
};

export const find = async (id: number): Promise<Item> => {
  const record: Item = items[id];

  if (record) {
    return record;
  }

  throw new Error("No record found");
};

To simulate the asynchronous nature of read and write operations, all the service methods are async methods. findAll returns the whole items store object, while find receives an id parameter that it uses to look up and return a single store element if found.

Next, define a method to create a new item in the store:

/**
 * Service Methods
 */

export const findAll = async (): Promise<Items> => {...};

export const find = async (id: number): Promise<Item> => {...};

export const create = async (newItem: Item): Promise<void> => {
  const id = new Date().valueOf();
  items[id] = {
    ...newItem,
    id
  };
};

The create method is simple: it receives an object of type Item as an argument, providing all the required values to define a new item in the store, except the item's id. To create a unique id value for each new element added to the store, you use the value of the current Date, which is based on the number of milliseconds between 1 January 1970 00:00:00 UTC and the current time.

Right below, add a method to update an existing store item:

/**
 * Service Methods
 */

export const findAll = async (): Promise<Items> => {...};

export const find = async (id: number): Promise<Item> => {...};

export const create = async (newItem: Item): Promise<void> => {...};

export const update = async (updatedItem: Item): Promise<void> => {
  if (items[updatedItem.id]) {
    items[updatedItem.id] = updatedItem;
    return;
  }

  throw new Error("No record found to update");
};

The update method receives an object of type Item as an argument. This time around, the value of the id property is included and used to find the item in the store to update it.

Finally, define a method to remove an item from the store:

/**
 * Service Methods
 */

export const findAll = async (): Promise<Items> => {...};

export const find = async (id: number): Promise<Item> => {...};

export const create = async (newItem: Item): Promise<void> => {...};

export const update = async (updatedItem: Item): Promise<void> => {...};

export const remove = async (id: number): Promise<void> => {
  const record: Item = items[id];

  if (record) {
    delete items[id];
    return;
  }

  throw new Error("No record found to delete");
};

The remove method receives an id value as a parameter and uses it to look up an item in the store and to delete it if found.

You've now completed the creation of a nimble service module using TypeScript and Node.js. Any other project can use the code of this service module as it's not tied to any particular framework. You use it in the next chapter to create your API controllers.

It's worth mentioning that you could have used a TypeScript class to define and encapsulate the service logic; however, using functions makes testing your service module much easier.

I've created data models and a data service using TypeScript