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:
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 itsRowKey
.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 itsRowKey
.
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 theCat
entity will be used as a table namecreateTableIfNotExists: 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!
- If
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.
Learn NestJS - Official NestJS Courses 📚
Level-up your NestJS and Node.js ecosystem skills in these incremental workshop-style courses, from the NestJS Creator himself, and help support the NestJS framework! 🐈🚀 The NestJS Fundamentals Course is now LIVE and 25% off for a limited time!
🎉 NEW - NestJS Course Extensions now live!
- NestJS Advanced Concepts Course now LIVE!
- NestJS Authentication / Authorization Course now LIVE!
- NestJS GraphQL Course (code-first & schema-first approaches) are now LIVE!
- NestJS Authentication / Authorization Course now LIVE!