🚀DynamoDB Best Practices Using Terraform, IAM Configurations, and NestJS with Repositories🚀
🚀 Optimizing Cloud-Native Applications with DynamoDB, Terraform, and NestJS
Developing scalable, secure, and modular applications is essential in today’s fast-paced tech landscape. Here's how I achieve it by combining:
1️⃣ Terraform for Infrastructure as Code (IaC)
2️⃣ IAM Configurations for Security and Access Control
3️⃣ NestJS with Repository Pattern for Clean Architecture
I follow a corporate-level folder structure for clarity, modularity, and scalability:
Folder Structure
project-root/
├── src/
│ ├── controllers/
│ │ └── user.controller.ts
│ ├── services/
│ │ └── user.service.ts
│ ├── models/
│ │ └── user.model.ts
│ ├── dynamoDB/
│ │ ├── dynamoDB.service.ts
│ │ └── dynamoDB.config.ts
│ ├── app.module.ts
│ ├── app.ts
│ └── main.ts
├── terraform/
│ ├── global/
│ │ └── main.tf
│ ├── common/
│ │ ├── dynamodb.tf
│ │ ├── iam.tf
│ │ └── s3.tf
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── stage/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── prod/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
1️⃣ Setting Up IAM Policy and Role for DynamoDB
Incorporate security best practices by defining least-privilege access with Terraform:
File: common/dynamodb.tf
This file sets up a reusable DynamoDB table configuration that can be referenced in different environments.
resource "aws_dynamodb_table" "main" {
name = var.table_name
billing_mode = "PAY_PER_REQUEST"
hash_key = "PK"
range_key = "SK"
attribute {
name = "PK"
type = "S"
}
attribute {
name = "SK"
type = "S"
}
tags = {
Environment = var.environment
}
}
output "table_name" {
value = aws_dynamodb_table.main.name
}
output "table_arn" {
value = aws_dynamodb_table.main.arn
}
common/iam.tf
resource "aws_iam_policy" "dynamodb_policy" {
name = "DynamoDBAccessPolicy"
description = "Policy to allow access to DynamoDB"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Sid = "DynamoDBReadWrite",
Effect = "Allow",
Action = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:Query",
"dynamodb:Scan"
],
Resource = "arn:aws:dynamodb:us-east-1:123456789012:table/UsersTable"
}
]
})
}
resource "aws_iam_role" "dynamodb_role" {
name = "DynamoDBAccessRole"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Principal = {
Service = "lambda.amazonaws.com"
},
Action = "sts:AssumeRole"
}
]
})
}
file common/s3.tf
This file defines a reusable S3 bucket configuration for different environments.
resource "aws_s3_bucket" "main" {
bucket = "${var.bucket_name}-${var.environment}"
acl = "private"
force_destroy = true
versioning {
enabled = true
}
tags = {
Environment = var.environment
}
}
output "bucket_name" {
value = aws_s3_bucket.main.bucket
}
output "bucket_arn" {
value = aws_s3_bucket.main.arn
}
2️⃣ DynamoDB Repository Pattern in NestJS
Leverage the repository pattern for cleaner, testable, and decoupled code:
DynamoDB Service:src/dynamoDB/dynamoDB.service.ts
import { Injectable } from '@nestjs/common';
import { DynamoDB } from 'aws-sdk';
@Injectable()
export class DynamoDBService {
private readonly dynamoDb = new DynamoDB.DocumentClient();
async getItem(params: DynamoDB.DocumentClient.GetItemInput) {
return await this.dynamoDb.get(params).promise();
}
async putItem(params: DynamoDB.DocumentClient.PutItemInput) {
return await this.dynamoDb.put(params).promise();
}
}
dynamoDB/dynamoDB.config.ts
Contains DynamoDB configurations.
export const DynamoDBConfig = {
tableName: 'UsersTable',
region: 'us-east-1',
};
user.controller.ts
Handles HTTP requests and delegates logic to the service layer.
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UserService } from '../services/user.service';
import { User } from '../models/user.model';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get(':id')
async getUser(@Param('id') id: string): Promise<User> {
return this.userService.getUserById(id);
}
@Post()
async createUser(@Body() user: User): Promise<void> {
await this.userService.createUser(user);
}
}
user.service.ts
Contains business logic.
import { Injectable } from '@nestjs/common';
import { UserRepository } from '../models/user.repository';
import { User } from '../models/user.model';
@Injectable()
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
async getUserById(id: string): Promise<User> {
return this.userRepository.findUserById(id);
}
async createUser(user: User): Promise<void> {
await this.userRepository.createUser(user);
}
}
user.model.ts
Defines the User model.
export class User {
userId: string;
name: string;
email: string;
}
app.module.ts
Registers all modules and services.
import { Module } from '@nestjs/common';
import { UserController } from './controllers/user.controller';
import { UserService } from './services/user.service';
import { DynamoDBService } from './dynamoDB/dynamoDB.service';
@Module({
imports: [],
controllers: [UserController],
providers: [UserService, DynamoDBService],
})
export class AppModule {}
app.ts
Contains application-level initialization logic.
import './app';
3️⃣ Environment-Specific Terraform Configurations
Use reusable modules and maintain separate configurations for dev, stage, and prod environments.
File: terraform/dev/main.tf
provider "aws" {
region = var.region
}
module "dynamodb" {
source = "../common"
table_name = var.table_name
}
module "iam" {
source = "../common"
policy_name = var.policy_name
role_name = var.role_name
dynamodb_arn = module.dynamodb.table_arn
}
File: terraform/dev/outputs.tf
output "dynamodb_table_name" {
description = "The name of the DynamoDB table"
value = module.dynamodb.table_name
}
output "dynamodb_table_arn" {
description = "The ARN of the DynamoDB table"
value = module.dynamodb.table_arn
}
output "iam_policy_arn" {
description = "The ARN of the IAM policy created"
value = module.iam.policy_arn
}
output "iam_role_arn" {
description = "The ARN of the IAM role created"
value = module.iam.role_arn
}
File: terraform/dev/variables.tf
variable "region" {
type = string
description = "AWS region for deployment"
}
variable "table_name" {
type = string
description = "Name of the DynamoDB table"
}
variable "policy_name" {
type = string
description = "Name of the IAM policy"
}
variable "role_name" {
type = string
description = "Name of the IAM role"
}
variable "environment" {
type = string
description = "The deployment environment (e.g., dev, stage, prod)"
}
variable "bucket_name" {
type = string
description = "The name of the S3 bucket"
}
variable "bucket_arn" {
type = string
description = "The name of the S3 bucket
}
Conclusion
Using this well-structured approach, you can build secure, scalable, and maintainable applications. Combining Terraform, IAM configurations, and NestJS ensures seamless integration between infrastructure and application layers.
💬 What are your best practices for cloud-native development? Share your thoughts in the comments!
#AWS #DynamoDB #Terraform #NestJS #RepositoryPattern #InfrastructureAsCode #CloudComputing #DevOps