Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
NODE_ENV=development
PORT=3000
JWT_SECRET=test-secret-key-for-development

# Database Configuration
TYPEORM_CONNECTION=mongodb
TYPEORM_HOST=localhost
TYPEORM_PORT=27017
TYPEORM_USERNAME=
TYPEORM_PASSWORD=
TYPEORM_DATABASE=nodejs_boilerplate

# Agenda Configuration
AGENDA_DB_COLLECTION=jobs
AGENDA_POOL_TIME=200
AGENDA_CONCURRENCY=10
Comment on lines +1 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not commit .env with secrets; switch to .env.example and gitignore .env.

Even dev secrets get reused; committing them is a recurring incident vector.

Suggested actions:

  • Remove .env from VCS, rotate any leaked secrets.
  • Add .env to .gitignore, keep a .env.example (no secrets).

Example .gitignore update:

+/.env
+/.env.*.local
+!.env.example

Example .env.example:

NODE_ENV=development
PORT=3000
JWT_SECRET=change-me
TYPEORM_CONNECTION=mongodb
TYPEORM_HOST=localhost
TYPEORM_PORT=27017
TYPEORM_USERNAME=
TYPEORM_PASSWORD=
TYPEORM_DATABASE=nodejs_boilerplate
AGENDA_DB_COLLECTION=jobs
AGENDA_POOL_TIME=200
AGENDA_CONCURRENCY=10

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 3-3: [UnorderedKey] The JWT_SECRET key should go before the NODE_ENV key

(UnorderedKey)


[warning] 10-10: [UnorderedKey] The TYPEORM_PASSWORD key should go before the TYPEORM_PORT key

(UnorderedKey)


[warning] 11-11: [UnorderedKey] The TYPEORM_DATABASE key should go before the TYPEORM_HOST key

(UnorderedKey)


[warning] 16-16: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 16-16: [UnorderedKey] The AGENDA_CONCURRENCY key should go before the AGENDA_DB_COLLECTION key

(UnorderedKey)

🤖 Prompt for AI Agents
.env lines 1-16: The committed .env contains secrets — remove the file from
version control, rotate any leaked secrets (e.g., JWT_SECRET), and stop tracking
it: add .env to .gitignore and run git rm --cached .env (then commit). Create
and commit a .env.example (no real secrets, use placeholders like
JWT_SECRET=change-me) containing the same keys shown in these lines so
developers have a template, and update repository docs/README to instruct
contributors to create their own local .env from .env.example.

30 changes: 30 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module.exports = {
parser: '@typescript-eslint/parser',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
plugins: ['@typescript-eslint', 'prettier'],
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
project: './tsconfig.json',
},
env: {
node: true,
es6: true,
jest: true,
},
rules: {
'prettier/prettier': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-inferrable-types': 'off',
'no-console': ['warn', { allow: ['warn', 'error'] }],
'max-len': ['error', { code: 160 }],
},
ignorePatterns: ['dist/', 'node_modules/', '*.js'],
};
25 changes: 25 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: [
'**/__tests__/**/*.ts',
'**/?(*.)+(spec|test).ts'
],
transform: {
'^.+\\.ts$': 'ts-jest',
},
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/main.ts',
],
coverageDirectory: 'coverage',
coverageReporters: [
'text',
'lcov',
'html'
],
moduleFileExtensions: ['ts', 'js', 'json'],
testTimeout: 30000,
};
Comment on lines +1 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Config uses ts-jest with Jest 30—this combo is risky today.

Given the versioning policy, ts-jest 29 targets Jest 29. Either:

  • Downgrade Jest to 29 (see package.json suggestion), or
  • Replace ts-jest with babel-jest (or swc) and add @babel/preset-typescript.

This prevents test runner failures. (npmjs.com)

🤖 Prompt for AI Agents
In jest.config.js around lines 1 to 25, the project currently uses ts-jest while
Jest 30 is installed which can cause incompatibilities; either pin Jest to v29
or migrate off ts-jest: to fix, choose one approach — (A) Downgrade Jest to v29
in package.json and reinstall so ts-jest v29 is matched (no config change
needed), or (B) Replace ts-jest by installing babel-jest and
@babel/preset-typescript, update devDependencies, remove the ts-jest transform
entry and instead configure transform to use babel-jest (and add a
babel.config.js with @babel/preset-typescript and @babel/preset-env), then
reinstall and run tests to confirm.

55 changes: 30 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,50 @@
"format": "prettier --write \"src/**/*.ts\"",
"start:prod": "node dist/main.js",
"start": "nodemon",
"lint": "tslint -p tsconfig.json -c tslint.json",
"test": "jest",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix",
"test": "jest --passWithNoTests",
"test:watch": "jest --watch",
"test:cov": "jest --coverage"
},
"engines": {
"node": ">=8.0.0"
"node": ">=18.0.0"
},
"dependencies": {
"agenda": "^5.0.0",
"argon2": "^0.31.1",
"body-parser": "^1.20.0",
"argon2": "^0.44.0",
"body-parser": "^2.2.0",
"celebrate": "^15.0.1",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.1",
"dotenv": "^17.2.1",
"express": "^5.1.0",
"express-jwt": "^8.4.1",
"helmet": "^7.0.0",
"joi": "^17.6.0",
"helmet": "^8.1.0",
"joi": "^18.0.1",
"jsonwebtoken": "^9.0.2",
"mongodb": "^6.2.0",
"reflect-metadata": "^0.1.13",
"mongodb": "^6.19.0",
"reflect-metadata": "^0.2.2",
"typedi": "^0.10.0",
"typeorm": "^0.3.10",
"typeorm": "^0.3.26",
"typeorm-typedi-extensions": "^0.4.1"
},
"devDependencies": {
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/jest": "^29.5.6",
"@types/morgan": "^1.9.3",
"@types/node": "^20.8.9",
"chai": "^4.3.10",
"jest": "^29.7.0",
"nodemon": "^3.0.1",
"prettier": "^3.0.3",
"ts-jest": "^29.1.1",
"ts-node": "^10.7.0",
"tslint": "^6.1.3",
"typescript": "^5.2.2"
"@types/cors": "^2.8.19",
"@types/express": "^5.0.3",
"@types/jest": "^30.0.0",
"@types/morgan": "^1.9.10",
"@types/node": "^24.3.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"chai": "^6.0.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"jest": "^30.1.2",
"nodemon": "^3.1.10",
"prettier": "^3.6.2",
"ts-jest": "^29.4.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.2"
}
Comment on lines +45 to 60
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Version mismatch: Jest 30 with ts-jest 29 is not supported.

ts-jest’s major tracks Jest’s major; 29.x is for Jest 29 and is not guaranteed for Jest 30. Either downgrade Jest to 29 or replace ts-jest (e.g., babel-jest).

  • Evidence: Jest 30 release/migration (Node 18+, TS ≥5.4). (github.com, jestjs.io)
  • ts-jest docs: “major number follows the one of Jest.” Latest is still 29.x. (npmjs.com)

Minimal fix (stay with ts-jest):

-    "@types/jest": "^30.0.0",
-    "jest": "^30.1.2",
+    "@types/jest": "^29.5.12",
+    "jest": "^29.7.0",
     "ts-jest": "^29.4.1",

Alternatively, keep Jest 30 and switch transforms to babel-jest; I can draft that if you prefer.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@types/jest": "^30.0.0",
"@types/morgan": "^1.9.10",
"@types/node": "^24.3.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"chai": "^6.0.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"jest": "^30.1.2",
"nodemon": "^3.1.10",
"prettier": "^3.6.2",
"ts-jest": "^29.4.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.2"
}
"@types/jest": "^29.5.12",
"@types/morgan": "^1.9.10",
"@types/node": "^24.3.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"chai": "^6.0.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"jest": "^29.7.0",
"nodemon": "^3.1.10",
"prettier": "^3.6.2",
"ts-jest": "^29.4.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.2"
🤖 Prompt for AI Agents
In package.json around lines 45-60, the project declares jest@30 while keeping
ts-jest@29 which is incompatible; fix by either downgrading jest to a 29.x
release that matches ts-jest (replace "jest": "^30.1.2" with a 29.x semver) or
remove ts-jest and switch to a Babel-based transformer (add babel-jest and the
appropriate @babel/preset-typescript and update the Jest transform config) —
pick the minimal option (downgrade Jest to 29.x) unless you want to convert the
test pipeline to babel-jest.

}
16 changes: 4 additions & 12 deletions src/Exception/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
interface ExtendedErrorInterface {
name: string;
message: string;
code: string;
status: number;
}

/**
* Exception to extend the error class to allow additional properties.
*
Expand All @@ -16,7 +9,9 @@ interface ExtendedErrorInterface {
* 3. code - Unique error code
* 4. link - Know more link
*/
class Exception extends Error {
export class Exception extends Error {
public status!: number;
public code?: string;
constructor(message: string, status: number = 500, code?: string, link?: string) {
Comment on lines +12 to 15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Prevent “/undefined” in dev link and reduce duplication.

When code is undefined, the message appends ${link}/undefined. Guard it.

-    if (isDev && link) {
-      message = `${message}\n> More details: ${link}/${code}`;
-    }
+    if (isDev && link) {
+      const linkSuffix = code ? `/${code}` : '';
+      message = `${message}\n> More details: ${link}${linkSuffix}`;
+    }

Also verify imports across the repo now use a named export:

Also applies to: 23-25


🏁 Script executed:

#!/usr/bin/env bash
rg -nP "import\s+Exception\s+from\s+['\"]\.\/?Exception" -g '!**/dist/**'

Length of output: 77


🏁 Script executed:

#!/usr/bin/env bash
set -eo pipefail
echo "🔍 Searching for default ESM imports of Exception..."
rg -nP "import\s+Exception\s+from\s+['\"][^'\"]*Exception['\"]" -g '!**/dist/**' || true

echo "🔍 Searching for CommonJS require calls importing Exception..."
rg -nP "require\(['\"][^'\"]*Exception['\"]\)" -g '!**/dist/**' || true

Length of output: 443


Guard against undefined code in dev link (src/Exception/index.ts lines 12–15 and 23–25)

Replace:

- if (isDev && link) {
-   message = `${message}\n> More details: ${link}/${code}`;
- }
+ if (isDev && link) {
+   const linkSuffix = code ? `/${code}` : '';
+   message = `${message}\n> More details: ${link}${linkSuffix}`;
+ }

Default imports of Exception were searched for and none found—named exports are correctly used.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export class Exception extends Error {
public status!: number;
public code?: string;
constructor(message: string, status: number = 500, code?: string, link?: string) {
if (isDev && link) {
const linkSuffix = code ? `/${code}` : '';
message = `${message}\n> More details: ${link}${linkSuffix}`;
}
🤖 Prompt for AI Agents
In src/Exception/index.ts around lines 12–15 and 23–25, the constructor/logic
assumes `code` is defined when building the dev link; guard against undefined by
only constructing the dev link if `code` is truthy (or provide a safe fallback
like 'unknown'), or by making the link optional. Modify the
constructor/assignment so it checks `if (code) { /* build link using code */ }
else { /* omit link or use fallback */ }` and ensure no template interpolation
uses an undefined `code`.

super(message);

Expand Down Expand Up @@ -74,7 +69,7 @@ class Exception extends Error {
/**
* Update the stack trace
*/
if (Error.hasOwnProperty('captureStackTrace')) {
if (Object.prototype.hasOwnProperty.call(Error, 'captureStackTrace')) {
Error.captureStackTrace(this, this.constructor);
return;
}
Expand All @@ -87,6 +82,3 @@ class Exception extends Error {
});
}
}

interface Exception extends ExtendedErrorInterface {}
export { Exception };
2 changes: 1 addition & 1 deletion src/api/middlewares/attachCurrentUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import UserService from '../../services/users';
* @param {*} res Express res Object
* @param {*} next Express next Function
*/
const attachCurrentUser = async (req, res, next) => {
const attachCurrentUser = async (req: any, res: any, next: any) => {
try {
const userServiceInstance = Container.get(UserService);
const userRecord = await userServiceInstance.findOne(req.auth.id);
Expand Down
8 changes: 4 additions & 4 deletions src/api/middlewares/isAuth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {expressjwt as jwt} from 'express-jwt';
import { expressjwt as jwt } from 'express-jwt';
import config from '../../config';

/**
Expand All @@ -7,21 +7,21 @@ import config from '../../config';
* Authorization: Bearer ${JWT}
*
*/
const getTokenFromHeader = (req) => {
const getTokenFromHeader = (req: any) => {
const { authorization } = req.headers;
if (
(authorization && authorization.split(' ')[0] === 'Token') ||
(authorization && authorization.split(' ')[0] === 'Bearer')
) {
return authorization.split(' ')[1];
}
// tslint:disable-next-line: no-null-keyword
// eslint-disable-next-line @typescript-eslint/no-null-keyword
return null;
Comment on lines +10 to 19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Token extractor: return undefined, drop broken ESLint disable, and handle scheme case-insensitively.

Prevents the reported rule error and makes parsing more robust.

Apply this diff:

-const getTokenFromHeader = (req: any) => {
-  const { authorization } = req.headers;
-  if (
-    (authorization && authorization.split(' ')[0] === 'Token') ||
-    (authorization && authorization.split(' ')[0] === 'Bearer')
-  ) {
-    return authorization.split(' ')[1];
-  }
-  // eslint-disable-next-line @typescript-eslint/no-null-keyword
-  return null;
-};
+const getTokenFromHeader = (req: any) => {
+  const { authorization } = req.headers || {};
+  if (!authorization) return undefined;
+  const [scheme, token] = authorization.split(' ');
+  if (!token) return undefined;
+  const s = (scheme || '').toLowerCase();
+  if (s === 'bearer' || s === 'token') return token;
+  return undefined;
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getTokenFromHeader = (req: any) => {
const { authorization } = req.headers;
if (
(authorization && authorization.split(' ')[0] === 'Token') ||
(authorization && authorization.split(' ')[0] === 'Bearer')
) {
return authorization.split(' ')[1];
}
// tslint:disable-next-line: no-null-keyword
// eslint-disable-next-line @typescript-eslint/no-null-keyword
return null;
const getTokenFromHeader = (req: any) => {
const { authorization } = req.headers || {};
if (!authorization) return undefined;
const [scheme, token] = authorization.split(' ');
if (!token) return undefined;
const s = (scheme || '').toLowerCase();
if (s === 'bearer' || s === 'token') return token;
return undefined;
};
🧰 Tools
🪛 ESLint

[error] 18-18: Definition for rule '@typescript-eslint/no-null-keyword' was not found.

(@typescript-eslint/no-null-keyword)

🤖 Prompt for AI Agents
In src/api/middlewares/isAuth.ts around lines 10 to 19, the token extractor
currently returns null, disables ESLint, and compares the auth scheme
case-sensitively; update it to return undefined (not null) to satisfy the
reported rule, remove the eslint-disable comment, and parse the Authorization
header robustly by checking header existence, splitting it once, lowercasing the
scheme for case-insensitive comparison against 'token' or 'bearer', and
returning the token part (or undefined if missing).

};

const isAuth = jwt({
algorithms: ['HS256'], // This is the default algorithm used by jsonwebtoken
secret: config.jwtSecret, // The _secret_ to sign the JWTs
secret: config.jwtSecret || 'default-secret-key', // The _secret_ to sign the JWTs
getToken: getTokenFromHeader, // How to extract the JWT from the request
Comment on lines +24 to 25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not ship with a default JWT secret. Fail fast if JWT_SECRET is missing.

Hardcoding a fallback like 'default-secret-key' is a critical security risk; tokens become trivially forgeable. Throw at startup unless explicitly running tests.

Apply this diff:

 import { expressjwt as jwt } from 'express-jwt';
 import config from '../../config';

+// Ensure JWT secret is configured (fail fast outside tests)
+if (!config.jwtSecret && process.env.NODE_ENV !== 'test') {
+  throw new Error('JWT secret is required. Set JWT_SECRET in the environment.');
+}
+
 const isAuth = jwt({
   algorithms: ['HS256'], // This is the default algorithm used by jsonwebtoken
-  secret: config.jwtSecret || 'default-secret-key', // The _secret_ to sign the JWTs
+  secret: config.jwtSecret as string, // The _secret_ to sign the JWTs
   getToken: getTokenFromHeader, // How to extract the JWT from the request
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
secret: config.jwtSecret || 'default-secret-key', // The _secret_ to sign the JWTs
getToken: getTokenFromHeader, // How to extract the JWT from the request
import { expressjwt as jwt } from 'express-jwt';
import config from '../../config';
// Ensure JWT secret is configured (fail fast outside tests)
if (!config.jwtSecret && process.env.NODE_ENV !== 'test') {
throw new Error('JWT secret is required. Set JWT_SECRET in the environment.');
}
const isAuth = jwt({
algorithms: ['HS256'], // This is the default algorithm used by jsonwebtoken
secret: config.jwtSecret as string, // The _secret_ to sign the JWTs
getToken: getTokenFromHeader, // How to extract the JWT from the request
});
🤖 Prompt for AI Agents
In src/api/middlewares/isAuth.ts around lines 24-25, the code supplies a
hardcoded fallback JWT secret ('default-secret-key'); remove that fallback and
instead require config.jwtSecret to be present, throwing an error (or exiting
startup) if it's missing unless the process is running in a test environment
(e.g., NODE_ENV === 'test'); ensure getTokenFromHeader remains unchanged and
that the module fails fast at startup with a clear message about the missing JWT
secret so the app never runs with a default insecure key.

});

Expand Down
2 changes: 1 addition & 1 deletion src/api/routes/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const route = Router();
// constructor(@Inject(AuthService) private authService: AuthService) {}
// }

export default (app) => {
export default (app: any) => {
app.use('/auth', route);

route.post(
Expand Down
12 changes: 6 additions & 6 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as dotenv from 'dotenv';
import dotenv from 'dotenv';

process.env.NODE_ENV = process.env.NODE_ENV || 'development';

const envFound = dotenv.config();
if (!envFound) {
if (envFound.error) {
// Throw generic error
throw new Error("Couldn't find .env file");
}
Expand All @@ -12,12 +12,12 @@ export default {
/**
* Application port.
*/
port: parseInt(process.env.PORT) || 3000,
port: parseInt(process.env.PORT || '3000'),

/**
* JWT Secret
*/
jwtSecret: process.env.JWT_SECRET,
jwtSecret: process.env.JWT_SECRET || 'default-secret-key',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not ship a default JWT secret.

Default secrets are a security risk. Require it in non-test envs.

-  jwtSecret: process.env.JWT_SECRET || 'default-secret-key',
+  jwtSecret: process.env.JWT_SECRET as string,

Add a guard (outside this hunk):

const isTest = process.env.NODE_ENV === 'test';
if (!process.env.JWT_SECRET && !isTest) {
  throw new Error('JWT_SECRET is required');
}
🤖 Prompt for AI Agents
In src/config/index.ts around line 20, the config currently falls back to a
hardcoded JWT default which is insecure; remove the default and require
JWT_SECRET in non-test environments by adding a guard near the top of this file
(outside the shown hunk) that checks NODE_ENV !== 'test' and throws an Error if
process.env.JWT_SECRET is missing, then use process.env.JWT_SECRET directly in
the exported config.


/**
* MongoDB connection options.
Expand All @@ -35,8 +35,8 @@ export default {
/**
* Database host port.
*/
// tslint:disable-next-line: radix
port: Number.parseInt(process.env.TYPEORM_PORT),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
port: Number.parseInt(process.env.TYPEORM_PORT!),
Comment on lines +38 to +39
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Fail-fast env parsing for DB port; avoid non-null assertion.

Number.parseInt(process.env.TYPEORM_PORT!, 10) yields NaN if unset. Provide default or throw with a clear error.

-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    port: Number.parseInt(process.env.TYPEORM_PORT!),
+    port: process.env.TYPEORM_PORT
+      ? Number.parseInt(process.env.TYPEORM_PORT, 10)
+      : undefined,

If port is required, add an explicit check near startup and fail with a descriptive message.


🏁 Script executed:

#!/bin/bash
set -e

echo "Occurrences of TYPEORM_PORT in src/config/index.ts:"
rg -nC3 'TYPEORM_PORT' src/config/index.ts

echo -e "\nFirst 100 lines of src/config/index.ts for context:"
sed -n '1,100p' src/config/index.ts

echo -e "\nTypeORM connection initialization sites in src/:"
rg -nC3 'createConnection' src/**/*.ts || true

Length of output: 2967


🏁 Script executed:

#!/bin/bash
set -e

echo "=== src/loaders/database.ts (first 200 lines) ==="
sed -n '1,200p' src/loaders/database.ts

Length of output: 1047


Add fail-fast validation for TYPEORM_PORT

  • In src/config/index.ts, remove the non-null assertion and inline Number.parseInt(process.env.TYPEORM_PORT!). Instead, before constructing database.port, do:

    const rawPort = process.env.TYPEORM_PORT;
    if (!rawPort) throw new Error('Missing required TYPEORM_PORT environment variable');
    const port = Number.parseInt(rawPort, 10);
    if (Number.isNaN(port)) throw new Error(`Invalid TYPEORM_PORT: ${rawPort}`);
    // …
    database: {
      // …
      port,
      // …
    },
🤖 Prompt for AI Agents
In src/config/index.ts around lines 38-39, remove the non-null assertion and
inline parse of process.env.TYPEORM_PORT; instead read process.env.TYPEORM_PORT
into a local rawPort, throw a descriptive Error if rawPort is missing, parse it
with Number.parseInt(rawPort, 10), throw a descriptive Error if the result is
NaN, and then assign the parsed numeric port value to database.port when
constructing the config.

/**
* Database username.
*/
Expand Down
8 changes: 4 additions & 4 deletions src/loaders/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as express from 'express';
import express from 'express';
import 'reflect-metadata';
import database from './database';
import server from './server';

export default async (app: express.Application) => {
const connection = await database();
console.log('DB loaded and connected!');
await database();
console.warn('DB loaded and connected!');

await server(app);
console.log('Server loaded!');
console.warn('Server loaded!');
};
12 changes: 6 additions & 6 deletions src/loaders/server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import bodyParser from 'body-parser';
import { errors, celebrate, isCelebrateError } from 'celebrate';
import { errors, isCelebrateError } from 'celebrate';
import cors from 'cors';
import * as express from 'express';
import express from 'express';
import helmet from 'helmet';
import routes from '../api/routes';

Expand All @@ -16,13 +16,13 @@ export default (app: express.Application) => {

/// catch 404 and forward to error handler
app.use((req, res, next) => {
const error: Error = new Error('Not Found');
error['status'] = 404;
const error: any = new Error('Not Found');
error.status = 404;
next(error);
});

/// error handlers
app.use((err, req, res, next) => {
app.use((err: any, req: any, res: any, next: any) => {
/**
* Handle 401 thrown by express-jwt library
*/
Expand All @@ -39,7 +39,7 @@ export default (app: express.Application) => {
return next(err);
});

app.use((err, req, res, next) => {
app.use((err: any, req: any, res: any, _next: any) => {
res.status(err.status || 500);
res.json({
errors: {
Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as express from 'express';
import express from 'express';
import config from './config';
import loaders from './loaders';

async function main() {
const app = express.default();
const app = express();
await loaders(app);

app.listen(config.port, () => {
Expand Down
16 changes: 8 additions & 8 deletions src/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,41 @@ export enum Role {
@Entity()
export class User {
@ObjectIdColumn()
public id: ObjectId;
public id!: ObjectId;

@Column()
public name: string;
public name!: string;

@Index({ unique: true })
@Column({
unique: true,
nullable: false,
transformer: [lowercase],
})
public email: string;
public email!: string;
Comment on lines 26 to +31
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid locale-dependent email lowercasing.

lowercase uses toLocaleLowerCase(), which can mis-handle emails in locales like Turkish. Prefer toLowerCase() and null-safety.

Apply in ValueTransformers.ts (outside this hunk):

 export const lowercase: ValueTransformer = {
-  to: (entityValue: string) => {
-    return entityValue.toLocaleLowerCase();
-  },
+  to: (entityValue: string) => entityValue?.toLowerCase(),
   from: (databaseValue: string) => {
     return databaseValue;
   },
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Column({
unique: true,
nullable: false,
transformer: [lowercase],
})
public email: string;
public email!: string;
// src/transformers/ValueTransformers.ts
export const lowercase: ValueTransformer = {
to: (entityValue: string) => entityValue?.toLowerCase(),
from: (databaseValue: string) => {
return databaseValue;
},
};
🤖 Prompt for AI Agents
In src/models/User.ts around lines 26-31 the email column uses a transformer
named `lowercase` which currently relies on locale-dependent
toLocaleLowerCase(); update the transformer implementation in
ValueTransformers.ts to use a null-safe, locale-independent conversion (e.g.,
return value == null ? value : String(value).toLowerCase()) and export/rename or
add it as `safeLowercase`; then update the model to use that transformer
(replace `lowercase` with the new `safeLowercase`) so emails are lowercased
deterministically and safely.


@Column({
select: false,
nullable: false,
})
public password: string;
public password!: string;

@Column({
select: false,
nullable: false,
})
public salt: string;
public salt!: string;

@Column({ type: 'enum', enum: Role, default: Role.User })
public role: Role;
public role!: Role;

@Column()
@CreateDateColumn()
public createdAt: Date;
public createdAt!: Date;

@Column()
@UpdateDateColumn()
public updatedAt: Date;
public updatedAt!: Date;

// @AfterLoad()
// public deletePropertis(): void {
Expand Down
Loading
Loading