??DynamoDB Best Practices Using Terraform, IAM Configurations, and NestJS with Repositories??
Kishan Maurya
Software Engineer @Capgemini | Node.js | Golang | Python | Microservices | System Design | AWS Cloud | DSA | @Redis Certified | @Meta Certified
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 #zomato #google #linkedIn #news #like #follow
Certified ForgeRock AM and CIAM 2X Specialist | OKTA 2X Certified | Transmit Consultant | Ex KPMG | JAVA | IAM | OpenAM | OpenDJ | AWS|
3 个月Great approach! Combining Terraform, IAM, and NestJS is a smart way to build scalable and secure applications. Clean architecture is always a win