loading...
Cover image for Introducing NoSQL Azure Table Storage for NestJS ๐Ÿš€
Microsoft Azure

Introducing NoSQL Azure Table Storage for NestJS ๐Ÿš€

wassimchegham profile image Wassim Chegham Originally published at trilon.io ใƒป7 min read

Originally published on the Trilon Blog on September 17, 2019.

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?

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:

Alt Text

Note down the "Storage account name" and "Connection string" obtained at Access keys under the 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 also install the dotenv package that allows us to deal with environment 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 versioned 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

Note: The API might slightly change in the stable release.

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 the 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

After 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 previous 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);
  }
}

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 straightforward 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);
    }
  }
}

Putting everything together

We have finished the implementation of our Cat feature. In this last step, we will need to 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 optional 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 exist 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 yourself before querying it!

In Conclusion

We just implemented a new Cat feature for our application that uses @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.

GitHub logo nestjs / azure-database

Azure Database (Table Storage and more) module for Nest framework (node.js) โ˜๏ธ

Nest Logo

A progressive Node.js framework for building efficient and scalable server-side applications.

NPM Version Package License NPM Downloads Travis Linux Coverage Gitter Backers on Open Collective Sponsors on Open Collective

Description

Azure Database (Table Storage, Cosmos DB and more) module for Nest framework (node.js)

Tutorial

Learn how to get started with Azure table storage for NestJS

Before Installation

For Table Storage

  1. Create a Storage account and resource (read more)
  2. For Table Storage, In the Azure Portal, go to Dashboard > Storage > your-storage-account.
  3. Note down the "Storage account name" and "Connection string" obtained at Access keys under Settings tab.

For Cosmos DB

  1. Create a Cosmos DB account and resource (read more)
  2. For Cosmos DB, In the Azure Portal, go to Dashboard > Azure Cosmos DB > your-cosmos-db-account.
  3. Note down the "URI" and "Primary Key" obtained at Keys under Settings tab.

Installation

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

Usage

For Azure Table Storage support

  1. Create or update your existingโ€ฆ

Posted on by:

wassimchegham profile

Wassim Chegham

@wassimchegham

Angular and Bazel contributor โ˜… JavaScript hacker at Microsoft โ˜… Creator of xlayers.dev, ngx.tools, nitr.ooo, autocap.cc, hexa.run, async-await.xyz โ˜… GDE for Google โ˜… Ambassador for Auth0

Microsoft Azure

Any language. Any platform.

Discussion

markdown guide