basic setup with modular monolith & auth#1
Conversation
There was a problem hiding this comment.
Pull request overview
This PR bootstraps a production-oriented Node.js/TypeScript backend boilerplate with a modular-monolith structure, adding core infrastructure (Docker/CI), configuration, and initial implementations for auth/RBAC, caching/queues, logging, and file storage.
Changes:
- Added core runtime setup (Express app + clustered entrypoint), environment/config modules, and foundational “core” utilities (errors/constants/types/container/db/cache/queue).
- Implemented initial feature modules (auth with session caching + BullMQ batching, admin config/roles/permissions, gallery with S3 presigned upload flows).
- Added production/dev tooling and infra (Docker/Docker Compose, CI workflow, ESLint/Prettier/Husky, Prisma config/schema, updated README).
Reviewed changes
Copilot reviewed 110 out of 114 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | TypeScript compiler settings + path aliases. |
| storage/keys/.gitignore | Ignores key subfolders under storage. |
| src/routes/index.ts | Root router + versioned module mounting. |
| src/index.ts | Server entrypoint + clustering + graceful shutdown. |
| src/database/schema.prisma | Initial Prisma schema (auth, roles, configs, gallery). |
| src/database/prisma.client.ts | Prisma client singleton using pg adapter/pool. |
| src/database/README.md | Database layer docs and commands. |
| src/database/.gitignore | Ignores migrations and generated Prisma client. |
| src/config/helmet.config.ts | Helmet security headers configuration. |
| src/config/environment.config.ts | Centralized env parsing and defaults. |
| src/config/cors.config.ts | Central CORS configuration. |
| src/app/http/services/storage/types.ts | Storage DTOs/constants and validation categories. |
| src/app/http/services/storage/storage.service.ts | S3 presigned storage manager + operations. |
| src/app/http/services/storage/index.ts | Storage service exports. |
| src/app/http/services/password.service.ts | PBKDF2 password hashing/verification. |
| src/app/http/services/logger.service.ts | Winston logger with request context support. |
| src/app/http/services/jwt.service.ts | RS256 JWT sign/verify helpers. |
| src/app/http/services/index.ts | Aggregated service exports. |
| src/app/http/services/helpers/health/types.ts | Health check result types. |
| src/app/http/services/helpers/health/redis.service.ts | Redis health check implementation. |
| src/app/http/services/helpers/health/key-file.service.ts | Key file existence/access health checks. |
| src/app/http/services/helpers/health/health.service.ts | Aggregated system health snapshot. |
| src/app/http/services/helpers/health/database.service.ts | DB connectivity health check (Prisma raw query). |
| src/app/http/services/encryption.service.ts | RSA encrypt/decrypt helper service. |
| src/app/http/modules/index.ts | HTTP modules router composition. |
| src/app/http/modules/gallery/validation.ts | Gallery request validation rules. |
| src/app/http/modules/gallery/types.ts | Gallery DTOs + response transformer. |
| src/app/http/modules/gallery/service.ts | Gallery business logic for uploads and retrieval. |
| src/app/http/modules/gallery/routes.ts | Gallery endpoints + permission checks. |
| src/app/http/modules/gallery/repository.ts | Gallery persistence methods. |
| src/app/http/modules/gallery/index.ts | Gallery module exports. |
| src/app/http/modules/gallery/controller.ts | Gallery controller endpoints. |
| src/app/http/modules/auth/validation.ts | Auth request validation rules. |
| src/app/http/modules/auth/types.ts | Auth DTOs and related types. |
| src/app/http/modules/auth/routes.ts | Auth routes (OTP/password/session mgmt). |
| src/app/http/modules/auth/repository.ts | Auth data access (users/sessions/OTP/reset). |
| src/app/http/modules/auth/queue.ts | BullMQ queue/worker for session lastUsed batching. |
| src/app/http/modules/auth/index.ts | Auth module exports (routes/cache/queue). |
| src/app/http/modules/auth/helpers.ts | Auth response helpers and account checks. |
| src/app/http/modules/auth/cache.ts | Session cache keying + invalidation in Redis. |
| src/app/http/modules/admin/roles/validation.ts | Role validation rules. |
| src/app/http/modules/admin/roles/types.ts | Role DTOs + response/entity types. |
| src/app/http/modules/admin/roles/service.ts | Role business logic + cache invalidation. |
| src/app/http/modules/admin/roles/routes.ts | Role endpoints + permission checks. |
| src/app/http/modules/admin/roles/repository.ts | Role persistence + permission upsert. |
| src/app/http/modules/admin/roles/index.ts | Roles module exports. |
| src/app/http/modules/admin/roles/controller.ts | Roles controller endpoints. |
| src/app/http/modules/admin/permissions/types.ts | Permissions response types. |
| src/app/http/modules/admin/permissions/service.ts | Permissions viewing logic. |
| src/app/http/modules/admin/permissions/routes.ts | Permissions endpoints. |
| src/app/http/modules/admin/permissions/repository.ts | Permissions data access with role joins. |
| src/app/http/modules/admin/permissions/index.ts | Permissions module exports. |
| src/app/http/modules/admin/permissions/controller.ts | Permissions controller endpoints. |
| src/app/http/modules/admin/index.ts | Admin router composition. |
| src/app/http/modules/admin/config/validation.ts | System config validation rules. |
| src/app/http/modules/admin/config/types.ts | System config DTOs/entities/responses. |
| src/app/http/modules/admin/config/service.ts | Config business logic + caching. |
| src/app/http/modules/admin/config/routes.ts | Config endpoints + permission checks. |
| src/app/http/modules/admin/config/repository.ts | Config persistence methods. |
| src/app/http/modules/admin/config/index.ts | Config module exports. |
| src/app/http/modules/admin/config/controller.ts | Config controller endpoints. |
| src/app/http/modules/admin/config/cache.ts | Redis-backed config cache operations. |
| src/app/http/middleware/sanitize.middleware.ts | Request input sanitization middleware. |
| src/app/http/middleware/request-context.middleware.ts | AsyncLocalStorage request context + request IDs. |
| src/app/http/middleware/rate-limit.middleware.ts | Rate limiting middleware with Redis store support. |
| src/app/http/middleware/logging.middleware.ts | HTTP request logging + error logging middleware. |
| src/app/http/middleware/index.ts | Middleware barrel exports. |
| src/app/http/middleware/error.middleware.ts | Global error + not-found handlers. |
| src/app/http/middleware/auth.middleware.ts | JWT auth + permission enforcement + session caching. |
| src/app/http/controllers/controller.ts | Base controller helpers + validation + responses. |
| src/app/http/controllers/app/app.controller.ts | Landing + health endpoints. |
| src/app/core/utils/sanitize.utils.ts | Log masking + XSS escape/sanitize helpers. |
| src/app/core/utils/random.utils.ts | Crypto-secure OTP/random generators. |
| src/app/core/utils/path.utils.ts | Project root resolution helper. |
| src/app/core/utils/index.ts | Utils barrel exports. |
| src/app/core/types/index.ts | Types barrel exports. |
| src/app/core/types/express.types.ts | Express request augmentation types. |
| src/app/core/types/common.types.ts | Shared types (request context). |
| src/app/core/types/api.types.ts | API response DTO types. |
| src/app/core/queue/index.ts | Queue barrel exports. |
| src/app/core/queue/bullmq.defaults.ts | Default BullMQ job options. |
| src/app/core/queue/bullmq.connection.ts | BullMQ Redis connection factory. |
| src/app/core/index.ts | Core barrel exports + init helpers. |
| src/app/core/errors/index.ts | AppError hierarchy + type guard. |
| src/app/core/database/prisma.client.ts | Core DB connect/disconnect wrappers. |
| src/app/core/database/index.ts | Core database exports. |
| src/app/core/database/base.repository.ts | Base repository with tx/raw query helpers. |
| src/app/core/container.ts | Simple DI container for core dependencies. |
| src/app/core/constants/roles.constants.ts | Role slug constants. |
| src/app/core/constants/permissions.constants.ts | RBAC module/action/scope constants + helpers. |
| src/app/core/constants/index.ts | Constants barrel exports. |
| src/app/core/constants/http.constants.ts | Error codes + rate limits + pagination constants. |
| src/app/core/constants/auth.constants.ts | Auth/JWT constants sourced from env config. |
| src/app/core/cache/redis.client.ts | Redis client singleton + connect/disconnect. |
| src/app/core/cache/index.ts | Cache barrel exports. |
| src/app.ts | Express app middleware chain + routes + error handling. |
| prisma.config.ts | Prisma config (schema/migrations/datasource). |
| package.json | Dependencies, scripts, engine constraints. |
| eslint.config.js | ESLint flat config + TS rules and restrictions. |
| env.example | Documented environment variables. |
| docker-compose.yml | Local infra stack (Postgres/Redis/backend). |
| README.md | Expanded developer documentation and usage. |
| Dockerfile | Multi-stage build + non-root runtime + healthcheck. |
| .prettierrc.json | Prettier formatting config. |
| .husky/pre-commit | Pre-commit hook (lint/build). |
| .gitignore | Repo ignore rules (env, logs, keys, Prisma client). |
| .github/workflows/ci.yaml | CI build + lint workflow. |
| .editorconfig | Editor formatting defaults. |
| .dockerignore | Docker build context exclusions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const projectRoot = getProjectRoot(); | ||
|
|
||
| // Load RSA keys for data encryption (separate from JWT keys) | ||
| const publicKey = fs.readFileSync(path.resolve(projectRoot, 'storage/keys/encryption/public.key'), 'utf8'); | ||
| const privateKey = fs.readFileSync(path.resolve(projectRoot, 'storage/keys/encryption/private.key'), 'utf8'); | ||
|
|
There was a problem hiding this comment.
Encryption keys are loaded synchronously at module import time. This will crash the process if key files are missing and makes it impossible to start the server to expose /health diagnostics. Load keys lazily (or validate them during a controlled startup step) and avoid readFileSync at module scope.
| const response: StorageConfirmResponse = { | ||
| success: true, | ||
| viewUrl: result.viewUrl ?? '' | ||
| }; | ||
| if (result.actualFileSize != null) { |
There was a problem hiding this comment.
On successful confirmation, viewUrl is set to result.viewUrl ?? ''. Returning an empty string on a “success” path can propagate an invalid URL to API consumers and hide a storage-provider regression. Consider treating a missing viewUrl as an error (return failure or throw) similarly to how generateUploadUrl/generateViewUrl validate required fields.
| (req as AuthenticatedRequest).session = { | ||
| id: sessionId, | ||
| userId: user.id, | ||
| token: decoded.t, | ||
| status: true, | ||
| expiresAt: cachedSession ? new Date(cachedSession.expiresAt) : new Date() | ||
| }; |
There was a problem hiding this comment.
expiresAt on the request session is set to new Date() when the session is loaded from DB (cache miss). That loses the real expiry time and can cause downstream logic (TTL calculations, client responses, authorization checks) to treat sessions as effectively expiring immediately or inconsistently. Populate expiresAt from the DB session record (and in the cached path keep using the cached value).
| openssl genrsa -out storage/keys/api/private.key 2048 | ||
| openssl rsa -in storage/keys/api/private.key -pubout -out storage/keys/api/public.key | ||
| openssl genrsa -out storage/keys/encryption/private.key 2048 | ||
| openssl rsa -in storage/keys/encryption/private.key -pubout -out storage/keys/encryption/private.key.pub |
There was a problem hiding this comment.
The encryption key generation command writes the public key to storage/keys/encryption/private.key.pub, but the runtime code reads storage/keys/encryption/public.key (see encryption.service.ts). Update the README commands/paths so the generated filenames match what the application expects.
| openssl rsa -in storage/keys/encryption/private.key -pubout -out storage/keys/encryption/private.key.pub | |
| openssl rsa -in storage/keys/encryption/private.key -pubout -out storage/keys/encryption/public.key |
| const projectRoot = getProjectRoot(); | ||
|
|
||
| // Read pepper/secret from key file (not stored in DB, adds extra layer of security) | ||
| const pepper = fs.readFileSync(path.resolve(projectRoot, 'storage/keys/encryption/private.key'), 'utf8'); | ||
|
|
There was a problem hiding this comment.
The password pepper is read synchronously from storage/keys/encryption/private.key at module import time. If the file is missing/unreadable, the server will crash on startup (and unit tests/imports will fail) before health checks can run. Prefer lazy loading (or an explicit init step) and surface a clear configuration error; also avoid readFileSync in module scope.
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '24' |
There was a problem hiding this comment.
CI uses Node.js 24 while package.json declares support for >=22 and the Dockerfile builds/runs on Node 22-alpine. Using a different major in CI can mask runtime issues or introduce Node-version-specific behavior. Consider aligning the workflow to the same major as production (e.g., 22) or testing a matrix that explicitly includes the production version.
| node-version: '24' | |
| node-version: '22' |
| const projectRoot = getProjectRoot(); | ||
| const logsDir = path.join(projectRoot, 'storage', 'logs'); | ||
|
|
There was a problem hiding this comment.
logsDir points to storage/logs, but that directory isn’t created here (and isn’t currently present under storage/). In production, winston-daily-rotate-file will fail to write/rotate if the directory doesn’t exist. Create the directory during startup (e.g., fs.mkdirSync(logsDir, { recursive: true })) or ensure it’s shipped/created in the container image at runtime.
| # Logs | ||
| *.log | ||
| storage/logs/* | ||
| !storage/logs/.gitkeep | ||
|
|
||
| # Environment files | ||
| .env | ||
| .env.* | ||
| !env.example | ||
|
|
There was a problem hiding this comment.
storage/keys/* is not excluded from the Docker build context. If a developer has generated RSA keys locally, COPY . . in the build stage can bake those private keys into the image layers. Add storage/keys/ (or at least storage/keys/**/*.key) to .dockerignore and provide keys to the container via secrets/volumes instead.
| origin: environment.cors?.origin, | ||
|
|
||
| /** | ||
| * Specifies the HTTP methods allowed for CORS requests. | ||
| * - Sourced from environment configuration (array of allowed methods). | ||
| * - Example: ["GET", "POST", "PUT", "DELETE", "OPTIONS"] | ||
| */ | ||
| methods: environment.cors?.methods, | ||
|
|
||
| /** | ||
| * Specifies the headers allowed in the actual request. | ||
| * - Allows common browser headers and custom headers. | ||
| */ | ||
| allowedHeaders: [ | ||
| 'Content-Type', | ||
| 'Authorization', | ||
| 'X-Requested-With', | ||
| 'Accept', | ||
| 'Origin', | ||
| 'Referer', | ||
| 'User-Agent', | ||
| 'sec-ch-ua', | ||
| 'sec-ch-ua-mobile', | ||
| 'sec-ch-ua-platform', | ||
| 'DNT' | ||
| ], | ||
|
|
||
| /** | ||
| * Indicates whether the response to the request can be exposed when credentials are present. | ||
| * - true: Allows cookies and credentials to be sent in cross-origin requests. | ||
| */ | ||
| credentials: true, |
There was a problem hiding this comment.
credentials: true combined with a default origin of ['*'] (from environment.config.ts) results in invalid CORS for credentialed requests (browsers will reject Access-Control-Allow-Origin: * when credentials are used). Consider either (a) defaulting origins to an explicit list and keeping credentials: true, or (b) turning off credentials when the origin list contains *, or (c) using an origin function that reflects the request origin when it’s allowed.
| const projectRoot = getProjectRoot(); | ||
|
|
||
| // Load RSA keys | ||
| const privateKey = fs.readFileSync(path.resolve(projectRoot, 'storage/keys/api/private.key'), 'utf8'); | ||
| const publicKey = fs.readFileSync(path.resolve(projectRoot, 'storage/keys/api/public.key'), 'utf8'); |
There was a problem hiding this comment.
JWT keys are loaded synchronously at module import time via readFileSync. If the key files are missing/misconfigured, the entire process will crash before the health check can report the issue and before the app can start gracefully. Load keys lazily inside generateTokenPair/verifyToken (or via an init step) with a clear startup-time validation error, and avoid synchronous file reads on the hot path.
This pull request introduces a comprehensive boilerplate setup for a Node.js backend application, focusing on modular architecture, robust development tooling, and production readiness. Key changes include the addition of configuration files for Docker, environment variables, code style enforcement, CI/CD, and core backend infrastructure, as well as documentation detailing the stack and usage.
Infrastructure & Environment Setup:
Dockerfilefor multi-stage Node.js builds and production runtime, including health checks and non-root user configuration.docker-compose.ymlto orchestrate PostgreSQL, Redis, and the backend service with health checks, persistent volumes, and network configuration..dockerignoreto optimize Docker builds by excluding unnecessary files and directories.env.examplewith detailed environment variable documentation for all major subsystems (database, Redis, JWT, logging, AWS S3, etc.).Development Tooling & Code Quality:
eslint.config.js) for TypeScript code style enforcement, including custom rules and project-specific restrictions..prettierrc.json) and EditorConfig (.editorconfig) for consistent code formatting across editors and CI. [1] [2].husky/pre-commit) to automatically lint, fix, and build code before commits.Continuous Integration:
.github/workflows/ci.yaml) for automated build, lint, and test steps on push and pull requests to the main branch.Documentation:
README.mdto document the tech stack, architecture, setup instructions, scripts, test users, API routes, and Docker usage for new developers.Core Backend Implementation:
src/app.tswith Express middleware chain for security, logging, rate limiting, error handling, and static file serving.src/app/core/cache/redis.client.tsand exported cache utilities insrc/app/core/cache/index.ts. [1] [2]prisma.config.tsfor schema, migrations, and datasource management.package.jsonwith scripts for build, dev, lint, typecheck, Prisma, and seeding, and declared all dependencies.These changes lay the foundation for a scalable, maintainable, and production-ready Node.js backend project.