DEV Community

Cover image for Introducing NoSQL Azure Table Storage for NestJS πŸš€
Wassim Chegham for Microsoft Azure

Posted on • Originally published at trilon.io

Introducing NoSQL Azure Table Storage for NestJS πŸš€

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>"
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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" }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 ...
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

The AzureTableStorageModule module takes in few optional arguments:

AzureTableStorageModule.forFeature(Cat, {
  table: 'AnotherTableName',
  createTableIfNotExists: true,
})
Enter fullscreen mode Exit fullscreen mode
  • 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 CosmosDB Database 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 Coverage Discord Backers on Open Collective Sponsors on Open Collective

Description

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

Disclaimer

You are reading the documentation for version 3. If you are looking for version 2 documentation, click here. Please also note that version 2 is no longer maintained and will not receive any updates!

Before Installation

For Cosmos DB (NoSQL ONLY)

  1. Create a Cosmos DB account and resource (read more)
  2. Note down the "URI", Database name and the "Primary Key" (or "Secondary Key") - You will need them later

For Table Storage

  1. Create a Storage account and resource (read more)
  2. Note down the "Connection string" - You will need it later

Installation

$ npm i --save @nestjs/azure-database
Enter fullscreen mode Exit fullscreen mode

Usage

For Azure Cosmos DB support

  1. Create or update your existing .env file with the following content:
AZURE_COSMOS_DB_NAME=
AZURE_COSMOS_DB_ENDPOINT=
…

Top comments (1)

Collapse
 
markpieszak profile image
Mark Pieszak • Edited

<3