Skip to content

Commit 70aa708

Browse files
Replace TypeORM by Drizzle (#60)
* Replace TypeORM by Drizzle * Remove useless code and set auto migrate optional * Refactor environment variable handling to improve value validation * Update README.md to reflect the transition from TypeORM to Drizzle ORM
1 parent 7a7e2fe commit 70aa708

File tree

21 files changed

+893
-499
lines changed

21 files changed

+893
-499
lines changed

.env.example

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
NODE_ENV=development
22
HOST=0.0.0.0
33
PORT=8000
4+
CORS_ORIGIN_ALLOWED=
5+
LOGGER_TYPE=console
6+
JWT_SECRET=2b0dbb9e26779c2e8aec40dd29eb45b6
7+
JWT_EXPIRES_IN_SECONDS=
8+
49
DB_USER=postgres
5-
DB_HOST=db
6-
DB_NAME=api_db
710
DB_PASSWORD=secret
11+
DB_NAME=api_db
12+
DB_HOST=db
813
DB_PORT=5432
914
DB_HOST_PORT=5433
10-
CORS_ORIGIN_ALLOWED=
11-
LOGGER_TYPE=console
12-
JWT_SECRET=secret
15+
DB_SSL=false
16+
DB_AUTO_MIGRATE=true
1317

1418
TEST_DB_HOST=db_test
1519
TEST_DB_NAME=test_db

README.md

Lines changed: 68 additions & 69 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,14 @@
1919
"docker:down": "docker-compose --profile dev --profile test down",
2020
"docker:shell": "docker exec -it backend sh",
2121
"docker:build": "docker build . -t backend",
22-
"typeorm": "typeorm-ts-node-commonjs",
23-
"migration:generate": "sh -c 'yarn typeorm migration:generate -d src/infra/database/config/data-source.orm.ts src/infra/database/migrations/$0'",
24-
"migration:create": "sh -c 'yarn typeorm migration:create src/infra/database/migrations/$0'",
25-
"migration:revert": "yarn typeorm migration:revert -d src/infra/database/config/data-source.orm.ts",
26-
"migration:run": "yarn typeorm migration:run -d src/infra/database/config/data-source.orm.ts"
22+
"migration:generate": "sh -c 'yarn drizzle-kit generate --config=./src/infra/database/config/drizzle.config.ts --name=$0'",
23+
"migration:run": "yarn drizzle-kit migrate --config=./src/infra/database/config/drizzle.config.ts"
2724
},
2825
"dependencies": {
2926
"bcryptjs": "3.0.2",
3027
"cors": "2.8.5",
3128
"dotenv": "17.2.2",
29+
"drizzle-orm": "^0.44.5",
3230
"express": "5.1.0",
3331
"helmet": "8.1.0",
3432
"inversify": "7.9.1",
@@ -37,7 +35,6 @@
3735
"pg": "8.16.3",
3836
"reflect-metadata": "0.2.2",
3937
"tslib": "2.8.1",
40-
"typeorm": "0.3.26",
4138
"zod": "4.1.7"
4239
},
4340
"devDependencies": {
@@ -51,7 +48,9 @@
5148
"@types/jsonwebtoken": "9.0.10",
5249
"@types/morgan": "1.9.10",
5350
"@types/node": "24.3.1",
51+
"@types/pg": "^8.15.5",
5452
"@types/supertest": "6.0.3",
53+
"drizzle-kit": "^0.31.4",
5554
"eslint": "9.35.0",
5655
"eslint-import-resolver-typescript": "4.4.4",
5756
"eslint-plugin-import": "2.32.0",

src/app/index.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import * as dotenv from 'dotenv';
21
import 'reflect-metadata';
3-
dotenv.config();
42

53
import { createServer } from '@/app/server';
64
import { env } from '@/core/env/env';
@@ -13,16 +11,17 @@ import type { ILogger } from '@/core/logger/logger.interface';
1311
const host = env('HOST', '0.0.0.0');
1412
const port = env('PORT', '8080');
1513

16-
setupContainer().then((container) => {
17-
const server = createServer(container);
18-
const database = container.get<IDatabase>(SERVICES_DI_TYPES.Database);
19-
const logger = container.get<ILogger>(CORE_DI_TYPES.Logger);
20-
database.initialize()
21-
.then(() => {
22-
server.listen({ host, port }, () => {
23-
logger.info(`⚡️ Server is running at http://${host}:${port}`);
24-
});
25-
})
26-
.catch(logger.error);
27-
})
14+
setupContainer()
15+
.then((container) => {
16+
const server = createServer(container);
17+
const database = container.get<IDatabase>(SERVICES_DI_TYPES.Database);
18+
const logger = container.get<ILogger>(CORE_DI_TYPES.Logger);
19+
database.initialize()
20+
.then(() => {
21+
server.listen({ host, port }, () => {
22+
logger.info(`⚡️ Server is running at http://${host}:${port}`);
23+
});
24+
})
25+
.catch(logger.error);
26+
})
2827
.catch((error) => { throw error; });

src/container/container.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,8 @@ import { registerRouters } from '@/container/routers/container';
1111

1212
type ContainerRegisterAction = (container: Container) => void | Promise<void>;
1313

14-
export type BuildContainerOptions = {
15-
onlyDatabase?: boolean;
16-
};
17-
18-
export const setupContainer = (options?: BuildContainerOptions) => {
19-
let builder;
20-
21-
if (options?.onlyDatabase) {
22-
builder = new ContainerBuilder().registerOnlyDatabase();
23-
} else {
24-
builder = new ContainerBuilder().register();
25-
}
14+
export const setupContainer = () => {
15+
const builder = new ContainerBuilder().register();
2616

2717
return builder.build();
2818
};
@@ -53,17 +43,12 @@ export class ContainerBuilder {
5343
return this;
5444
}
5545

56-
registerOnlyDatabase() {
57-
this.registerServices({ onlyDatabase: true });
58-
return this;
59-
}
60-
6146
private registerCore() {
6247
return registerCore(this);
6348
}
6449

65-
private registerServices(options?: BuildContainerOptions) {
66-
return registerServices(this, options);
50+
private registerServices() {
51+
return registerServices(this);
6752
}
6853

6954
private registerRepositories() {

src/container/services/container.ts

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { BuildContainerOptions, ContainerBuilder } from '@/container/container';
1+
import type { ContainerBuilder } from '@/container/container';
22
import { REPOSITORIES_DI_TYPES } from '@/container/repositories/di-types';
33
import { SERVICES_DI_TYPES } from '@/container/services/di-types';
4-
import { integerEnv, mandatoryEnv, mandatoryIntegerEnv, booleanEnv, env } from '@/core/env/env';
4+
import { booleanEnv, integerEnv, mandatoryEnv, mandatoryIntegerEnv } from '@/core/env/env';
55
import type { IUserRepository } from '@/domain/repositories/user-repository.interface';
66
import type { IAuthenticator } from '@/domain/services/auth/authenticator.interface';
77
import type { IEncryptor } from '@/domain/services/security/encryptor.interface';
@@ -10,16 +10,8 @@ import type { IDatabase, DatabaseConfig } from '@/infra/database/database';
1010
import { Database } from '@/infra/database/database';
1111
import { BcryptEncryptor } from '@/infra/security/encryptor/bcrypt-encryptor';
1212

13-
14-
export const registerServices = (containerBuilder: ContainerBuilder, options?: BuildContainerOptions) => {
15-
let builder;
16-
17-
if (options?.onlyDatabase) {
18-
builder = new ServicesContainerBuilder(containerBuilder).registerOnlyDatabase();
19-
} else {
20-
builder = new ServicesContainerBuilder(containerBuilder).registerServices();
21-
}
22-
13+
export const registerServices = (containerBuilder: ContainerBuilder) => {
14+
const builder = new ServicesContainerBuilder(containerBuilder).registerServices();
2315
return builder;
2416
};
2517

@@ -38,12 +30,6 @@ class ServicesContainerBuilder {
3830
return this.containerBuilder;
3931
}
4032

41-
registerOnlyDatabase() {
42-
this.registerDatabaseService();
43-
44-
return this.containerBuilder;
45-
}
46-
4733
private registerAuthServices() {
4834
const config = {
4935
secret: mandatoryEnv('JWT_SECRET'),
@@ -81,17 +67,22 @@ class ServicesContainerBuilder {
8167
private getDatabaseConfig(): DatabaseConfig {
8268
const isTest = mandatoryEnv('NODE_ENV') === 'test';
8369

70+
const host = isTest ? mandatoryEnv('TEST_DB_HOST') : mandatoryEnv('DB_HOST');
71+
const port = isTest ? mandatoryIntegerEnv('TEST_DB_PORT') : mandatoryIntegerEnv('DB_PORT');
72+
const username = mandatoryEnv('DB_USER');
73+
const password = mandatoryEnv('DB_PASSWORD');
74+
const database = isTest ? mandatoryEnv('TEST_DB_NAME') : mandatoryEnv('DB_NAME');
75+
const ssl = isTest ? false : booleanEnv('DB_SSL', false);
76+
const autoMigrate = booleanEnv('DB_AUTO_MIGRATE', true);
77+
8478
return {
85-
type: 'postgres',
86-
host: isTest ? mandatoryEnv('TEST_DB_HOST') : mandatoryEnv('DB_HOST'),
87-
port: isTest ? mandatoryIntegerEnv('TEST_DB_PORT') : mandatoryIntegerEnv('DB_PORT'),
88-
username: mandatoryEnv('DB_USER'),
89-
password: mandatoryEnv('DB_PASSWORD'),
90-
database: isTest ? mandatoryEnv('TEST_DB_NAME') : mandatoryEnv('DB_NAME'),
91-
logging: booleanEnv('DB_LOGGING', false),
92-
migrationsRun: isTest,
93-
entities: [env('TYPEORM_ENTITIES', 'src/infra/database/models/**/*.entity.ts')],
94-
migrations: [env('TYPEORM_MIGRATIONS', 'src/infra/database/migrations/**/*.ts')],
79+
host,
80+
port,
81+
username,
82+
password,
83+
database,
84+
ssl,
85+
autoMigrate,
9586
};
9687
}
9788
}

src/core/env/env.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1-
export const env = (key: string, defaultValue: string): string => process.env[key] ?? defaultValue;
1+
import { config } from 'dotenv';
2+
3+
config();
4+
5+
export function isDefined<T>(value: T | undefined): value is T {
6+
return value !== undefined && value !== '';
7+
}
8+
9+
export const env = (key: string, defaultValue: string): string => {
10+
const value = process.env[key];
11+
return isDefined(value) ? value : defaultValue;
12+
};
213

314
export const mandatoryEnv = (key: string): string => {
415
const value = process.env[key];
5-
if (value === undefined) {
16+
if (!isDefined(value)) {
617
throw new Error(`Variable ${key} not found in environment`);
718
}
819

@@ -11,13 +22,13 @@ export const mandatoryEnv = (key: string): string => {
1122

1223
export const integerEnv = (key: string, defaultValue: number): number => {
1324
const value = process.env[key];
14-
return value === undefined ? defaultValue : parseInt(value, 10);
25+
return isDefined(value) ? parseInt(value, 10) : defaultValue;
1526
};
1627

1728
export const mandatoryIntegerEnv = (key: string): number => {
1829
const value = process.env[key];
1930

20-
if (value === undefined) {
31+
if (!isDefined(value)) {
2132
throw new Error(`Variable ${key} not found in environment`);
2233
}
2334

@@ -27,17 +38,19 @@ export const mandatoryIntegerEnv = (key: string): number => {
2738
export const booleanEnv = (key: string, defaultValue: boolean): boolean => {
2839
const value = process.env[key];
2940

30-
if (value !== undefined && value !== 'true' && value !== 'false') {
41+
const isDefinedValue = isDefined(value);
42+
43+
if (isDefinedValue && value !== 'true' && value !== 'false') {
3144
throw new Error(`Variable ${key} must be a boolean`);
3245
}
3346

34-
return value === undefined ? defaultValue : value === 'true';
47+
return isDefinedValue ? value === 'true' : defaultValue;
3548
};
3649

3750
export const mandatoryBooleanEnv = (key: string): boolean => {
3851
const value = process.env[key];
3952

40-
if (value === undefined) {
53+
if (!isDefined(value)) {
4154
throw new Error(`Variable ${key} not found in environment`);
4255
}
4356

@@ -51,9 +64,11 @@ export const mandatoryBooleanEnv = (key: string): boolean => {
5164
export const unionEnv = <T extends string>(key: string, values: T[], defaultValue: T): T => {
5265
const value = process.env[key];
5366

54-
if (value !== undefined && !values.includes(value as T)) {
67+
const isDefinedValue = isDefined(value);
68+
69+
if (isDefinedValue && !values.includes(value as T)) {
5570
throw new Error(`Variable ${key} must be one of ${values.join(', ')}`);
5671
}
5772

58-
return value === undefined ? defaultValue : value as T;
73+
return isDefinedValue ? value as T : defaultValue;
5974
};

src/infra/database/config/data-source.orm.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { defineConfig } from 'drizzle-kit';
2+
3+
import { booleanEnv, mandatoryEnv, mandatoryIntegerEnv } from '@/core/env/env';
4+
5+
const isTest = mandatoryEnv('NODE_ENV') === 'test';
6+
7+
const host = isTest ? mandatoryEnv('TEST_DB_HOST') : mandatoryEnv('DB_HOST');
8+
const port = isTest ? mandatoryIntegerEnv('TEST_DB_PORT') : mandatoryIntegerEnv('DB_PORT');
9+
const username = mandatoryEnv('DB_USER');
10+
const password = mandatoryEnv('DB_PASSWORD');
11+
const database = isTest ? mandatoryEnv('TEST_DB_NAME') : mandatoryEnv('DB_NAME');
12+
const ssl = isTest ? false : booleanEnv('DB_SSL', false);
13+
14+
export default defineConfig({
15+
schema: 'src/infra/database/schemas',
16+
out: 'src/infra/database/migrations',
17+
dialect: 'postgresql',
18+
dbCredentials: {
19+
host,
20+
port,
21+
user: username,
22+
password,
23+
database,
24+
ssl,
25+
}
26+
});

0 commit comments

Comments
 (0)