Starting from this chapter?

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

git clone git@github.com:auth0-blog/wab-menu-api-nestjs.git \
nest-restaurant-api \
--branch getting-started

Make the project folder your current directory:

cd nest-restaurant-api

Then, install the project dependencies:

npm i

Finally, create a .env hidden file:

touch .env

Populate .env with this:

PORT=7000

Organizing NestJS Apps Using Modules

NestJS allows you to effectively organize your application through modules. Each module encapsulates elements of your application that are closely related, keeping your project organized and helping you establish clear topology boundaries. As your application or contributors grow in size, a predictable architecture helps you tame complexity down while observing the SOLID principles of software engineering.

In NestJS, each application has at least one module, the root module, which NestJS uses as the starting point to build your application graph — an internal data structure used to track and resolve the relationships between different elements of your application. Any additional modules are considered feature modules that encapsulate code relevant to a specific feature of your application.

Your application will have three modules to start with:

  • AppModule: the root module which has already been created.

  • ItemsModule: a feature module that holds code related to the items resource.

  • AuthModule: a feature module that handles anything related to authentication and authorization.

AppModule will depend on these two feature modules to bring your application together.

Start by creating an ItemsModule using the NestJS CLI:

npx nest generate module items

The NestJS CLI creates an items subdirectory under the src directory to host your module files. An items.module.ts file defining ItemsModule is added to this subdirectory.

As you can see, a module is a class annotated with a @Module() decorator, which provides metadata that NestJS uses to organize the application structure.

Open src/app.module.ts and notice that ItemsModule was automatically added to its imports array:

// src/app.module.ts

import { Module } from '@nestjs/common';
import { ItemsModule } from './items/items.module';

@Module({
  imports: [ItemsModule],
  controllers: [],
  providers: [],
})
export class AppModule {}

You won't have to manually update the dependencies of your modules. By using the NestJS CLI, all your modules will be updated automatically as you create elements related to each feature, such as classes, controllers, and services.

"As an architecture framework, NestJS takes the guesswork out of project structure and lets you focus on what matters the most: building robust applications"

Creating Data Models with TypeScript

Before creating any controllers and services, you'll define the structure of the data you'll store. 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 application, you have at your disposal different strategies to help you define and enforce the structure of objects during development. You can use either classes or interfaces to define custom types.

NestJS rightfully recommends using classes as they are preserved as real entities in the compiled JavaScript. Another advantage of using classes is that you can use decorators on its member variables to enhance them. This will prove helpful when you add data validation to your application later on.

Using the CLI generate an Item class:

npx nest generate class item --no-spec

This time, npx uses your project installation of the NestJS CLI.

NestJS creates an item.ts file under the src directory for you. Open this file and populate it like so:

// src/item.ts

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

You'll put these objects of type Item in a key-value store. The id property will serve as the key and the Item object as the value.

Using the CLI generate an Items class:

npx nest generate class items --no-spec

Update the newly created src/items.ts file as follows:

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

As you'll use these class definitions to type the methods of controllers and services that you'll create in the next sections, it's fine for these files to live under the src directory.

Creating a NestJS Service

A service lets you encapsulate related business logic that can be shared across multiple projects. NestJS services are classes marked with the @Injectable decorator, which makes them injectable into other components. Your application will use a service to access and manipulate records from your store.

Use the NestJS CLI to create an ItemsService as follows:

npx nest generate service items --no-spec

The --no-spec flag prevents the generation of files related to testing.

NestJS creates an items.service.ts file under the src/items directory. The newly defined ItemsService is also automatically registered as a provider of ItemsModule.

// src/items/items.module.ts

import { Module } from '@nestjs/common';
import { ItemsService } from './items.service';

@Module({
  providers: [ItemsService],
})
export class ItemsModule {}

ItemsService will hold an array where all your records will be saved. You won't connect to a database, but the data will be persisted in memory while the application is running. You will then create methods to perform data operations on the store to fulfill the business requirements of your application.

Update src/items/items.service.ts with the following code:

// src/items/items.service.ts

import { Injectable } from '@nestjs/common';
import { Item } from '../item';
import { Items } from '../items';

@Injectable()
export class ItemsService {
  private readonly 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',
    },
  };

  findAll(): Items {
    return this.items;
  }

  create(newItem: Item) {
    const id = new Date().valueOf();
    this.items[id] = {
      ...newItem,
      id,
    };
  }

  find(id: number): Item {
    const record: Item = this.items[id];

    if (record) {
      return record;
    }

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

  update(updatedItem: Item) {
    if (this.items[updatedItem.id]) {
      this.items[updatedItem.id] = updatedItem;
      return;
    }

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

  delete(id: number) {
    const record: Item = this.items[id];

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

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

By adding all the logic to query data in a service, you make your app modular. As of now, you are storing the records in memory. However, if the business requirements changed to use MongoDB as a store, you need to only make changes in your service, without having to open and update any of its consumers.

Notice that the create method creates a unique id property automatically for you when a new Item object is created.

const id = new Date().valueOf();

This value is based on the number of milliseconds between 1 January 1970 00:00:00 UTC and the current time.

Checkpoint

Add your current project files to the repository:

git add .

Commit the file bundle as follows:

git commit -m "Create data models and a data service"

I've created my NestJS data models and a data service