Introducing NoSQL Azure Table Storage for NestJS .

Wassim Chegham | Trilon Consulting
Wassim Chegham

In this article we'll be looking at how to add Azure Table Storage to our NestJS applications in only a few minutes with the new @nestjs/azure-database library!

In case you're not familiar with NestJS, it is a TypeScript Node.js framework that helps you build enterprise-grade efficient and scalable Node.js applications.

What's Azure Table Storage?

For more information on how to run Serverless NestJS apps with Azure read the full article here

Azure Table Storage is a NoSQL key-value store using massive semi-structured datasets.

Table Storage allows you to create massively-scalable apps that require a flexible data schema. You can also perform OData-based queries and use JSON to serialize data.

Use Azure Table storage to store petabytes of semi-structured data and keep costs down.

Unlike many data stores—on-premises or cloud-based:

  • Table storage lets you scale up without having to manually shard your dataset.
  • Availability also isn’t a concern!
    • Using geo-redundant storage, stored data is replicated three times within a region—and an additional three times in another region, hundreds of miles away.
Let's dive into how we can use Table Storage for our NestJS applications!

Getting setup

NOTE: In this demonstration, we'll be showcasing a new NestJS application generated by the CLI, but if you prefer to use an existing NestJS application - feel free - and just skip ahead !

Generate a new NestJS Application

For demo purposes, let's make sure we have the latest NestJS CLI installed - and create a New application.

$ npm i -g @nestjs/cli
$ nest new PROJECT_NAME

Now let's cd into the newly created directory and open up our IDE. At this point we have a simple generated NestJS Application.

Setting up an Azure Storage account

In order to use Table Storage we will need to create an Azure Storage account.
You can follow this step by step guide.

Once our storage account created, we need to copy the Connection String that we will use for our SDK. In the Azure Portal, go to Dashboard > Storage > your-storage-account:

image

Note down the "Storage account name" and "Connection string" obtained at Access keys under Settings tab.

TIP: The connection string should start with DefaultEndpointsProtocol=

NestJS Azure Storage Installation

Next, we need to install the @nestjs/azure-database SDK from NPM:

$ npm i --save @nestjs/azure-database dotenv

We are also install the dotenv package that allows us to deal with environmement variables.

We will then create a file called .env with the following content:

AZURE_STORAGE_CONNECTION_STRING="<the connection string we copied from previous step>"

Also very important: we will make sure to add our .env file to the .gitignore! The .env file MUST NOT be versionned on Git.

Once the .env file created and ready, we will include the following call to the src/main.ts file:

if (process.env.NODE_ENV !== 'production') require('dotenv').config();
TIP: This line must be added before any other imports in the src/main.ts file!

Our setup is now ready. Let's implement the logic of our application.


Preparing our business logic

The Azure Table Storage support in NestJS follows the Object-Relational Mapping (ORM) design pattern which is basically a "structured" way of accessing a database from our code - letting you use an API instead of writing actual SQL code.

In order to implement this design pattern we will need to create the following components, for each feature:

  • DTO (or data transfer object)
    • This is the object that will represent our data. DTO's are primarily used to transfer data between application services, such as that between an HTTP service and a browser.
  • Entity
    • This is basically a class mapped to the table schema.
  • Repository
    • This is the component that is responsible for communicating with the database.

Let's first create a NestJS feature module where we will host our feature business logic. We will use the NestJS CLI to create a Cat feature:

$ nest generate module cat

NOTE: We will come back to our generated module at the end of the process.

DTO

The first component we need to create for our Cat feature is a DTO. Inside a file named cat.dto.ts, we create the following class:

export class CatDTO {
  name: string;
  age: number;
}

Entity

Next, we need an Entity. To do so, we create a file called cat.entity.ts and describe the model using the decorators provided by @nestjs/azure-database:

Entity Represents Required
@EntityPartitionKey(value: string) The PartitionKey of the entity Yes
@EntityRowKey(value: string) The RowKey of the entity Yes
@EntityInt32(value?: string) Signed 32-bit integer values
@EntityInt64(value?: string) Signed 64-bit integer values
@EntityBinary(value?: string) Binary (blob) data
@EntityBoolean(value?: string) true or false values
@EntityString(value?: string) Character data
@EntityDouble(value?: string) Floating point numbers with 15 digit precision
@EntityDateTime(value?: string) Time of day

For instance, the shape of the following entity:

import {
  EntityPartitionKey,
  EntityRowKey,
  EntityString,
  EntityIn32
} from '@nestjs/azure-database';

@EntityPartitionKey('CatID')
@EntityRowKey('CatName')
export class Cat {
  @EntityString() name: string;
  @EntityIn32() age: number;
}

The Cat entity will be automatically converted to following schema that is expected by the Azure Table Storage:

{
  "PartitionKey": { "_": "CatID", "$": "Edm.String" },
  "RowKey": { "_": "CatName", "$": "Edm.String" },
  "name": { "_": undefined, "$": "Edm.String" },
  "age": { "_": undefined, "$": "Edm.Int32" }
}

Repository

Aftr the DTO and the Entity, we need now to create a Cat service that will abstract all the CRUD operations related to the Cat entity. This service will use the Azure Table Storage Repository.

Let's create a service using the NestJS CLI:

$ nest generate service cat

Inside the created cat.service.ts, we import the Repository and provide it with the Cat entity definition created in the provious step:

import { Injectable } from '@nestjs/common';
import { Repository, InjectRepository } from '@nestjs/azure-database';
import { Cat } from './cat.entity';

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(Cat)
    private readonly catRepository: Repository<Cat>,
  ) {}

    // ... other code ...

The Azure Table Storage Repository interface provides a bunch of public APIs and types for managing various CRUD (Create, Read, Update and Delete) operations. Let's see how we can implement each different operation using @nestjs/azure-database SDK.

The methods we will be invoking are the following:

  • create(entity: T): Promise<T> to create a new entity.
  • findAll(tableQuery?: azure.TableQuery, currentToken?: azure.TableService.TableContinuationToken): Promise<AzureTableStorageResultList<T>>
    to find all entities that match the given query (return all entities if no query provided).
  • find(rowKey: string, entity: Partial<T>): Promise<T> to find one entity using its RowKey.
  • update(rowKey: string, entity: Partial<T>): Promise<T> to update an entity. This does a partial update.
  • delete(rowKey: string, entity: T): Promise<AzureTableStorageResponse> to remove an entity using its RowKey.

Here is an example of such implementation:

import { Injectable } from '@nestjs/common';
import { Repository, InjectRepository } from '@nestjs/azure-database';
import { Cat } from './cat.entity';

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(Cat)
    private readonly catRepository: Repository<Cat>,
  ) {}

  // find one cat entitu by its rowKey
  async find(rowKey: string, cat: Cat): Promise<Cat> {
    return this.catRepository.find(rowKey, cat);
  }

  // find all cat entities
  async findAll(): Promise<AzureTableStorageResultList<Cat>> {
    return this.catRepository.findAll();
  }

  // create a new cat entity
  async create(cat: Cat): Promise<Cat> {
    return this.catRepository.create(cat);
  }

  // update the a cat entity by its rowKey
  async update(rowKey: string, cat: Partial<Cat>): Promise<Cat> {
    return this.catRepository.update(rowKey, cat);
  }

  // delete a cat entity by its rowKey
  async delete(rowKey: string, cat: Cat): Promise<AzureTableStorageResponse> {
    return this.catRepository.delete(rowKey, cat);
  }
}

NOTE: Filtering, sorting and ordering operations will be added soon.

Controller

The last step is to implement the NestJS controller that will process the HTTP requests. Let's create such a controller using the NestJS CLI:

$ nest generate controller cat

The implementation of the controller is straightfoward and would probably depend on your application business needs. Here is an example of an implementation:

import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Post,
  Put,
  UnprocessableEntityException,
  NotFoundException,
  Patch
} from '@nestjs/common';
import { CatDto } from './cat.dto';
import { Cat } from './cat.entity';
import { CatService } from './cat.service';

@Controller('cats')
export class CatController {
  constructor(private readonly catService: CatService) {}

  @Get()
  async getAllCats() {
    return await this.catService.findAll();
  }

  @Get(':rowKey')
  async getCat(@Param('rowKey') rowKey) {
    try {
      return await this.catService.find(rowKey, new Cat());
    } catch (error) {
      // Entity not found
      throw new NotFoundException(error);
    }
  }

  @Post()
  async createCat(
    @Body()
    catData: CatDto,
  ) {
    try {
      const cat = new Cat();
      // Disclaimer: Assign only the properties you are expecting!
      Object.assign(cat, catData);

      return await this.catService.create(cat);
    } catch (error) {
      throw new UnprocessableEntityException(error);
    }
  }

  @Put(':rowKey')
  async saveCat(@Param('rowKey') rowKey, @Body() catData: CatDto) {
    try {
      const cat = new Cat();
      // Disclaimer: Assign only the properties you are expecting!
      Object.assign(cat, catData);

      return await this.catService.update(rowKey, cat);
    } catch (error) {
      throw new UnprocessableEntityException(error);
    }
  }

  @Patch(':rowKey')
  async updateCatDetails(@Param('rowKey') rowKey, @Body() catData: Partial<CatDto>) {
    try {
      const cat = new Cat();
      // Disclaimer: Assign only the properties you are expecting!
      Object.assign(cat, catData);

      return await this.catService.update(rowKey, cat);
    } catch (error) {
      throw new UnprocessableEntityException(error);
    }
  }

  @Delete(':rowKey')
  async deleteDelete(@Param('rowKey') rowKey) {
    try {
      const response = await this.catService.delete(rowKey, new Cat());

      if (response.statusCode === 204) {
        return null;
      } else {
        throw new UnprocessableEntityException(response);
      }
    } catch (error) {
      throw new UnprocessableEntityException(error);
    }
  }
}

Puting everything all together

He have finished the implementation of our Cat feature. In this last step, we will need import the AzureTableStorageModule inside our Nest feature module cat.module.ts that we created earlier:

import { Module } from '@nestjs/common';
import { AzureTableStorageModule } from '@nestjs/azure-database';
import { CatController } from './cat.controller';
import { CatService } from './cat.service';
import { Cat } from './cat.entity';

@Module({
  imports: [AzureTableStorageModule.forFeature(Cat)],
  providers: [CatService],
  controllers: [CatController],
})
export class CatModule {}

The AzureTableStorageModule module takes in few optionnal arguments:

AzureTableStorageModule.forFeature(Cat, {
  table: 'AnotherTableName',
  createTableIfNotExists: true,
})
  • table: string: The name of the table. If not provided, the name of the Cat entity will be used as a table name
  • createTableIfNotExists: boolean: Whether to automatically create the table if it doesn't exists or not:
    • If true the table will be created during the startup of the app.
    • If false the table will not be created. You will have to create the table by yoursel before querying it!

In Conclusion

We just implemented a new Cat feature for our application that use @nestjs/azure-database official package to add support for Azure Table Storage. With NestJS's modular system, we are able to install it and set it up with our application almost like a native Nest-feature!

If you're interested in learning more about Serverless NestJS apps with Azure read more here.

#NestJS
#Azure
#Serverless

Share this Post!

More from the Trilon Blog .

Kamil Mysliwiec | Trilon Consulting
Kamil Mysliwiec

Announcing NestJS Monorepos and new CLI commands

Learn how to manage multiple NestJS projects in a single monorepo using the NEW NestJS CLI features, and other great new tools!

Read More
Kamil Mysliwiec | Trilon Consulting
Kamil Mysliwiec

Deploy NestJS Serverless Apps to Azure Functions

Learn about the new NestJS Schematics and how to setup & deploy NestJS Serverless apps to Azure Functions in a few minutes!

Read More
Mark Pieszak | Trilon Consulting
Mark Pieszak

How to Delete ALL node_modules folders on your machine

Learn how to free up space on your Mac/PC by recursively deleting ALL node_modules folders in 1-line of code!

Read More

What we do at Trilon .

At Trilon, our goal is to help elevate teams - giving them the push they need to truly succeed in today's ever-changing tech world.

Trilon - Consulting

Consulting .

Let us help take your Application to the next level - planning the next big steps, reviewing architecture, and brainstorming with the team to ensure you achieve your most ambitious goals!

Trilon - Development and Team Augmentation

Development .

Trilon can become part of your development process, making sure that you're building enterprise-grade, scalable applications with best-practices in mind, all while getting things done better and faster!

Trilon - Workshops on NestJS, Node, and other modern JavaScript topics

Workshops .

Have a Trilon team member come to YOU! Get your team up to speed with guided workshops on a huge variety of topics. Modern NodeJS (or NestJS) development, JavaScript frameworks, Reactive Programming, or anything in between! We've got you covered.

Trilon - Open-source contributors

Open-source .

We love open-source because we love giving back to the community! We help maintain & contribute to some of the largest open-source projects, and hope to always share our knowledge with the world!

Let us know how we can help you!

Contact Us

Write us a message .

Let's talk about how Trilon can help your next project get to the next level.

Rather send us an email? Write to:

hello@trilon.io
© 2019 Trilon, Inc.