Skip to content

Commit edc39a8

Browse files
authored
add dynamodb persistence (#10)
1 parent 00bd252 commit edc39a8

16 files changed

+1467
-305
lines changed

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
registry=https://registry.npmjs.org/

api/.env-dev

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
MACHINE_ID=1
2-
PORT = 3000
2+
PORT=3000
33
# Perhpas we should only use a domain name here instead of a full URL (with the port as it is error prone)
4-
SHORTENED_BASE_URL = http://localhost:3000
5-
# redis | memory
6-
CACHE_PERSISTENCE= memory
7-
REDIS_URL = redis://localhost:6379
4+
SHORTENED_BASE_URL=http://localhost:3000
5+
# You can set this property to false for rapid testing. It will use in-memory storage rather than pulling Redis and DynamoDB
6+
USE_PERSISTENT_STORAGE=false
7+
REDIS_URL=redis://localhost:6379
88
# 10 minutes
9-
REDIS_TTL = 600
9+
REDIS_TTL=600
10+
AWS_REGION=us-east-1
11+
DYNAMO_TABLE=shortUrls
12+
# for local development only
13+
DYNAMO_ENDPOINT=http://localhost:8000
14+
AWS_ACCESS_KEY_ID=dummy
15+
AWS_SECRET_ACCESS_KEY=dummy

api/.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
registry=https://registry.npmjs.org/

api/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
"test:e2e": "jest --config ./test/jest-e2e.json"
2222
},
2323
"dependencies": {
24+
"@aws-sdk/client-dynamodb": "3.775.0",
25+
"@aws-sdk/credential-providers": "3.775.0",
26+
"@aws-sdk/lib-dynamodb": "3.775.0",
2427
"@nestjs/common": "10.0.0",
2528
"@nestjs/config": "4.0.0",
2629
"@nestjs/core": "10.0.0",
Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
import { Module, DynamicModule } from '@nestjs/common';
2-
import { ConfigModule, ConfigService } from '@nestjs/config';
2+
import { ConfigModule } from '@nestjs/config';
33
import { ShortenUrlRepository } from '../shorten-url/shorten-url.repository';
4-
import { RepositoryProvider } from './repository/repository.provider';
4+
import { ShortenUrlRepositoryFactory } from './repository/shorten-url/shorten-url-repository.factory';
5+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
6+
import { DynamoClientProvider } from './provider/dynamodb-client.provider';
7+
import { RedisClientProvider } from './provider/redis-client.provider';
58

69
@Module({
7-
imports: [ConfigModule], // ✅ Ensure ConfigModule is loaded
10+
imports: [ConfigModule],
811
})
912
export class InfrastructureModule {
1013
static register(): DynamicModule {
1114
return {
1215
module: InfrastructureModule,
13-
providers: [RepositoryProvider],
14-
exports: [ShortenUrlRepository], // ✅ Export repository so other modules can use it
16+
providers: [
17+
RedisClientProvider,
18+
DynamoClientProvider,
19+
ShortenUrlRepositoryFactory,
20+
],
21+
exports: [RedisClientProvider, DynamoDBClient, ShortenUrlRepository],
1522
};
1623
}
1724
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// infrastructure/providers/dynamodb-client.provider.ts
2+
import { Logger, Provider } from '@nestjs/common';
3+
import { ConfigService } from '@nestjs/config';
4+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
5+
import { fromEnv } from '@aws-sdk/credential-providers';
6+
7+
export const DynamoClientProvider: Provider = {
8+
provide: DynamoDBClient,
9+
useFactory: (configService: ConfigService): DynamoDBClient => {
10+
const usePersistent = configService.get<string>(
11+
'USE_PERSISTENT_STORAGE',
12+
'false',
13+
);
14+
15+
if (usePersistent === 'false') {
16+
Logger.log('Skipping DynamoDB client creation (non-persistent mode)');
17+
return null;
18+
}
19+
20+
return new DynamoDBClient({
21+
region: configService.get('AWS_REGION', 'local'),
22+
endpoint: configService.get('DYNAMO_ENDPOINT', 'http://localhost:8000'),
23+
credentials: fromEnv({}),
24+
});
25+
},
26+
inject: [ConfigService],
27+
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {
2+
Injectable,
3+
Logger,
4+
OnModuleInit,
5+
OnModuleDestroy,
6+
} from '@nestjs/common';
7+
import { ConfigService } from '@nestjs/config';
8+
import { createClient, RedisClientType } from 'redis';
9+
10+
@Injectable()
11+
export class RedisClientProvider implements OnModuleInit, OnModuleDestroy {
12+
private client: RedisClientType | null = null;
13+
14+
constructor(private readonly configService: ConfigService) {}
15+
16+
async get(key: string): Promise<string | null> {
17+
return this.client.get(key);
18+
}
19+
20+
async set(key: string, value: string, ttlInSeconds: number): Promise<void> {
21+
await this.client.set(key, value, { EX: ttlInSeconds });
22+
}
23+
24+
async onModuleInit() {
25+
const usePersistent = this.configService.get<string>(
26+
'USE_PERSISTENT_STORAGE',
27+
'false',
28+
);
29+
30+
if (usePersistent === 'false') {
31+
Logger.log('🧪 Skipping Redis connection (non-persistent mode)');
32+
return;
33+
}
34+
35+
const redisUrl = this.configService.get<string>('REDIS_URL');
36+
this.client = createClient({ url: redisUrl });
37+
38+
this.client.on('error', (err) =>
39+
Logger.error('Redis client error', err, 'REDIS_CLIENT'),
40+
);
41+
42+
await this.client.connect();
43+
Logger.log('Connected to Redis');
44+
}
45+
46+
async onModuleDestroy() {
47+
if (this.client) {
48+
await this.client.quit();
49+
Logger.log('Redis connection closed');
50+
}
51+
}
52+
}

api/src/infrastructure/repository/redis-url.repository.ts

Lines changed: 0 additions & 56 deletions
This file was deleted.

api/src/infrastructure/repository/repository.provider.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

api/src/infrastructure/repository/in-memory-url.repository.ts renamed to api/src/infrastructure/repository/shorten-url/in-memory-url-storage.repository.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { ShortenUrlRepository } from '../../shorten-url/shorten-url.repository';
1+
import { ShortenUrlRepository } from '../../../shorten-url/shorten-url.repository';
22
import { Injectable, Logger } from '@nestjs/common';
33

44
@Injectable()
5-
export class InMemoryUrlRepository implements ShortenUrlRepository {
5+
export class InMemoryUrlStorageRepository implements ShortenUrlRepository {
66
private urls: Map<string, string> = new Map();
77

88
create(url: string, encodedUrl: string): Promise<void> {

0 commit comments

Comments
 (0)