Skip to content

Comments

Cloud-native MEAN boilerplate#1

Closed
LouisLetcher wants to merge 3 commits intomainfrom
cursor/cloud-native-mean-boilerplate-0dc4
Closed

Cloud-native MEAN boilerplate#1
LouisLetcher wants to merge 3 commits intomainfrom
cursor/cloud-native-mean-boilerplate-0dc4

Conversation

@LouisLetcher
Copy link
Contributor

Initial commit for a comprehensive, modern, cloud-native MEAN stack boilerplate, providing a robust foundation for new projects adhering to 12-Factor App principles.


Open in Web Open in Cursor 

@cursor
Copy link

cursor bot commented Feb 20, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @LouisLetcher, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request lays the foundational groundwork for a modern, cloud-native MEAN stack application. It provides a fully configured environment for rapid development, emphasizing best practices such as containerization, strict TypeScript, and robust code quality tooling. The changes enable developers to quickly spin up a new project with a well-defined architecture for both the API and the user interface.

Highlights

  • Initial Boilerplate Setup: Introduced a complete cloud-native MEAN stack boilerplate, adhering to 12-Factor App principles, with full TypeScript implementation for both frontend and backend.
  • Containerization with Docker: Integrated Docker and Docker Compose for streamlined development, deployment, and orchestration of the backend (Node.js/Express), frontend (Angular 17+ with Nginx), and MongoDB services.
  • Comprehensive Code Quality and DX: Configured ESLint, Prettier, Husky (with lint-staged and commitlint) across the monorepo to enforce consistent code style, quality, and commit message standards.
  • Backend API Structure: Established a robust Express.js backend with modular routes (health, users, auth), environment variable validation (Zod), centralized error handling, API rate limiting, and structured logging (Pino).
  • Modern Angular Frontend: Set up an Angular 17+ frontend utilizing standalone components, signals, routing, API service integration, and HTTP interceptors for authentication and error handling.
Changelog
  • .env.example
    • Added example environment variables for application, backend, frontend, MongoDB, security, and JWT settings.
  • .eslintrc.json
    • Added root ESLint configuration for consistent code style and quality across the monorepo, extending recommended TypeScript and Prettier rules.
  • .gitignore
    • Added common ignore patterns for build artifacts, node modules, environment files, and IDE-specific files.
  • .husky/commit-msg
    • Added a Husky hook to run commitlint on commit messages, enforcing conventional commit standards.
  • .husky/pre-commit
    • Added a Husky hook to run lint-staged before committing, ensuring staged files are formatted and linted.
  • .prettierignore
    • Added Prettier ignore patterns for generated files and lock files.
  • .prettierrc
    • Added Prettier configuration for consistent code formatting.
  • README.md
    • Updated the README with a comprehensive overview of the boilerplate, technology stack, directory structure, quick start guides for local and Docker Compose development, API endpoints, configuration details, code quality tools, testing instructions, and CI/CD information.
  • backend/.env.example
    • Added backend-specific example environment variables for server, MongoDB, security, and JWT settings.
  • backend/.eslintrc.json
    • Added backend-specific ESLint configuration, extending the root config and adding a rule for unused variables.
  • backend/Dockerfile
    • Added a multi-stage Dockerfile for building and running the Node.js Express backend in a production-ready container.
  • backend/jest.config.js
    • Added Jest configuration for backend unit and integration testing with TypeScript support.
  • backend/package.json
    • Added backend-specific dependencies (Express, Mongoose, Pino, Zod, etc.) and development dependencies (Jest, Supertest, tsx) along with scripts for development, build, linting, and testing.
  • backend/src/app.ts
    • Added the main Express application setup, including middleware for security (helmet, cors), JSON parsing, logging (pino-http), rate limiting, and routing for health, users, and authentication modules.
  • backend/src/config/env.ts
    • Added environment variable validation using Zod, ensuring required configuration is present and correctly typed at application startup.
  • backend/src/config/index.ts
    • Added an index file to export configuration utilities.
  • backend/src/db/mongodb.ts
    • Added MongoDB connection and disconnection logic using Mongoose, including retry mechanisms and connection event listeners.
  • backend/src/middleware/errorHandler.ts
    • Added a centralized error handling middleware for Express, standardizing API error responses and logging unhandled errors.
  • backend/src/middleware/index.ts
    • Added an index file to export middleware functions.
  • backend/src/middleware/rateLimiter.ts
    • Added an API rate limiting middleware using 'express-rate-limit' with configurable window and max requests.
  • backend/src/modules/auth/auth.controller.ts
    • Added placeholder controller functions for user login and fetching current user details, with notes for future JWT implementation.
  • backend/src/modules/auth/auth.routes.ts
    • Added Express routes for authentication, mapping '/login' and '/me' to their respective controller functions.
  • backend/src/modules/auth/index.ts
    • Added an index file to export authentication routes.
  • backend/src/modules/health/health.controller.ts
    • Added controller functions for liveness and readiness probes, including a MongoDB connection check for readiness.
  • backend/src/modules/health/health.routes.ts
    • Added Express routes for health checks, mapping '/liveness' and '/readiness' to their controller functions.
  • backend/src/modules/health/index.ts
    • Added an index file to export health routes.
  • backend/src/modules/users/index.ts
    • Added an index file to export user routes and the User model.
  • backend/src/modules/users/user.controller.ts
    • Added controller functions for listing all users with pagination and fetching a user by ID.
  • backend/src/modules/users/user.model.ts
    • Added a Mongoose schema and model for users, defining fields like email and name with validation and timestamps.
  • backend/src/modules/users/user.routes.ts
    • Added Express routes for user management, mapping '/' for listing and '/:id' for fetching by ID.
  • backend/src/server.ts
    • Added the main server bootstrap logic, connecting to MongoDB and starting the Express application, with graceful shutdown handling.
  • backend/src/utils/AppError.ts
    • Added a custom AppError class for standardized application-specific error handling with HTTP status codes and error codes.
  • backend/src/utils/asyncHandler.ts
    • Added an asyncHandler utility to wrap Express route handlers, automatically catching and forwarding asynchronous errors to the error middleware.
  • backend/src/utils/logger.ts
    • Added a structured JSON logger using Pino, configured for different environments.
  • backend/tests/health.test.ts
    • Added integration tests for the health endpoints using Supertest and MongoMemoryServer.
  • backend/tsconfig.json
    • Added TypeScript configuration for the backend, specifying target, module resolution, strictness, and output directories.
  • commitlint.config.js
    • Added commitlint configuration to enforce conventional commit message format and length.
  • docker-compose.yml
    • Added a Docker Compose file to define and link services for the backend, frontend, and MongoDB, including health checks and environment variable mapping.
  • frontend/.env.example
    • Added example environment variables for the frontend, specifically for the API base URL.
  • frontend/.eslintrc.json
    • Added frontend-specific ESLint configuration, extending the root config and Angular-specific rules.
  • frontend/Dockerfile
    • Added a multi-stage Dockerfile for building the Angular frontend and serving it with Nginx in a production container.
  • frontend/angular.json
    • Added the Angular CLI configuration file, defining project settings, build options, and linting rules for the 'mean-frontend' application.
  • frontend/karma.conf.js
    • Added Karma test runner configuration for the Angular frontend, including Jasmine and coverage reporters.
  • frontend/nginx.conf
    • Added Nginx configuration to serve the Angular application and proxy API and health check requests to the backend service.
  • frontend/package.json
    • Added frontend-specific dependencies (Angular core, router, http, etc.) and development dependencies (Angular CLI, ESLint, Karma) along with scripts for development, build, linting, and testing.
  • frontend/src/app/app.component.spec.ts
    • Added basic unit tests for the Angular root component.
  • frontend/src/app/app.component.ts
    • Added the Angular root component, defining the main application layout with header, navigation, main content area, and footer, using standalone components and SCSS styles.
  • frontend/src/app/app.config.ts
    • Added the Angular application configuration, providing zone change detection, router, and HTTP client with auth and error interceptors.
  • frontend/src/app/app.routes.ts
    • Added the main Angular application routes, including lazy-loaded feature modules for home and authentication.
  • frontend/src/app/core/guards/auth.guard.ts
    • Added an Angular authentication guard to protect routes, checking for a token in local storage and redirecting to login if unauthorized.
  • frontend/src/app/core/interceptors/auth.interceptor.ts
    • Added an Angular HTTP interceptor to automatically attach a JWT token (if present in local storage) to outgoing API requests.
  • frontend/src/app/core/interceptors/error.interceptor.ts
    • Added an Angular HTTP interceptor for global API error handling, logging errors and redirecting to the login page on 401 Unauthorized responses.
  • frontend/src/app/core/services/api.service.ts
    • Added a generic Angular API service for making HTTP requests, encapsulating base URL and providing methods for GET, POST, PUT, and DELETE.
  • frontend/src/app/features/auth/auth.routes.ts
    • Added feature-specific routes for the authentication module, including a login component.
  • frontend/src/app/features/auth/login/login.component.ts
    • Added an Angular login component with a reactive form, basic validation, and placeholder logic for API interaction and token handling.
  • frontend/src/app/features/home/home.component.spec.ts
    • Added basic unit tests for the Angular home component.
  • frontend/src/app/features/home/home.component.ts
    • Added an Angular home component that displays a welcome message and fetches API health status, using signals for state management.
  • frontend/src/environments/environment.prod.ts
    • Added production environment configuration for the Angular application, setting production to true and apiUrl to an empty string for relative proxying.
  • frontend/src/environments/environment.ts
    • Added development environment configuration for the Angular application, setting production to false and apiUrl to the local backend address.
  • frontend/src/index.html
    • Added the base HTML file for the Angular single-page application, including meta tags and a custom favicon.
  • frontend/src/main.ts
    • Added the entry point for bootstrapping the Angular application.
  • frontend/src/styles/main.scss
    • Added global SCSS styles, defining CSS variables for a dark theme, basic typography, and reset rules.
  • frontend/tsconfig.app.json
    • Added TypeScript configuration specific to the Angular application build.
  • frontend/tsconfig.json
    • Added root TypeScript configuration for the Angular project, defining compiler options for strictness, module resolution, and Angular-specific settings.
  • frontend/tsconfig.spec.json
    • Added TypeScript configuration specific to Angular test files.
  • package.json
    • Added root package.json with monorepo scripts for installing, linting, formatting, and managing Docker services for both backend and frontend, along with development dependencies for Husky, commitlint, and Prettier.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/ci.yml
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is a great start for a comprehensive MEAN stack boilerplate. The project structure is well-organized, and it incorporates many modern best practices like TypeScript, Docker, environment variable validation with Zod, and a solid code quality setup with ESLint, Prettier, and Husky. The backend is robust with features like rate limiting, structured logging, and graceful shutdown hooks. The frontend uses modern Angular with standalone components and signals. My review focuses on further improving robustness, developer experience, and adherence to best practices, particularly around configuration management, Docker setup, and resource handling. I've identified a few areas for improvement, including a critical issue with missing environment variables in the Docker setup that would break authentication, and some suggestions to make the configuration less confusing and the shutdown process more reliable.

Comment on lines +15 to +16
RATE_LIMIT_WINDOW_MS: ${RATE_LIMIT_WINDOW_MS:-900000}
RATE_LIMIT_MAX: ${RATE_LIMIT_MAX:-100}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The backend service is missing the JWT_SECRET and JWT_EXPIRES_IN environment variables. The application relies on these for the authentication module, but they are not being passed to the Docker container. This will cause authentication to fail in the Dockerized environment. You should add them to the environment section, sourcing them from the .env file. Using ${JWT_SECRET:?error message} will cause docker-compose to fail if the variable is not set, which is a good practice for required secrets.

      RATE_LIMIT_WINDOW_MS: ${RATE_LIMIT_WINDOW_MS:-900000}
      RATE_LIMIT_MAX: ${RATE_LIMIT_MAX:-100}
      JWT_SECRET: ${JWT_SECRET:?JWT_SECRET must be set in .env file}
      JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-7d}

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./

RUN npm ci --only=production && npm cache clean --force
Copy link
Contributor

Choose a reason for hiding this comment

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

high

To fix the healthcheck defined in docker-compose.yml, you need to install a utility like wget in this production image, as it's not included in node:20-alpine by default.

RUN npm ci --only=production && npm cache clean --force
RUN apk add --no-cache wget

Comment on lines +15 to +21
const shutdown = async (signal: string): Promise<void> => {
logger.info({ signal }, 'Received shutdown signal');
server.close(() => {
logger.info('HTTP server closed');
process.exit(0);
});
};
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The graceful shutdown handler closes the HTTP server but does not disconnect from MongoDB. This can leave open connections to the database and potentially lead to resource leaks. You should call disconnectMongo() as part of the shutdown process to ensure a clean exit.

Suggested change
const shutdown = async (signal: string): Promise<void> => {
logger.info({ signal }, 'Received shutdown signal');
server.close(() => {
logger.info('HTTP server closed');
process.exit(0);
});
};
const shutdown = (signal: string): void => {
logger.info({ signal }, 'Received shutdown signal');
server.close(async () => {
logger.info('HTTP server closed');
await disconnectMongo();
process.exit(0);
});
};

mongodb:
condition: service_healthy
healthcheck:
test: ['CMD', 'wget', '-q', '-O', '-', 'http://localhost:3000/health/readiness']
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The healthcheck for the backend service uses wget, but the node:20-alpine base image specified in backend/Dockerfile does not include wget by default. This will cause the healthcheck to fail, and Docker may incorrectly report the service as unhealthy. You should install wget in the backend's Dockerfile. I've added a corresponding suggestion on the backend/Dockerfile.

Comment on lines +63 to +64
cp .env.example .env
cp backend/.env.example backend/.env
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The setup requires two separate .env files (.env at the root and backend/.env) with several overlapping variables (e.g., NODE_ENV, MONGODB_URI, JWT_SECRET). This can be confusing for developers and prone to configuration errors. Consider simplifying this to a single .env file at the project root. The backend application can be configured (e.g., using the dotenv package's path option) to read from the root-level file during local development.

"build": "tsc",
"start": "node dist/server.js",
"lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\"",
"test": "jest --runInBand --forceExit",
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The test script includes the --forceExit flag. This is generally discouraged as it can mask underlying issues in your tests, such as open handles (e.g., database connections, timers) that prevent Jest from exiting gracefully. It's better to ensure your tests clean up all resources properly.

Suggested change
"test": "jest --runInBand --forceExit",
"test": "jest --runInBand",

CORS_ORIGIN: z.string().default('http://localhost:4200'),
RATE_LIMIT_WINDOW_MS: z.coerce.number().positive().default(900000),
RATE_LIMIT_MAX: z.coerce.number().positive().default(100),
JWT_SECRET: z.string().optional(),
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

JWT_SECRET is defined as optional in the environment schema. However, if the authentication module is used, a missing secret will cause runtime errors. It's better to enforce its presence at startup to fail fast. Consider making it a required variable by removing .optional() and ensuring it's not an empty string.

Suggested change
JWT_SECRET: z.string().optional(),
JWT_SECRET: z.string().min(1),

Comment on lines +24 to +38
export function loadConfig(): Env {
const result = envSchema.safeParse(process.env);

if (!result.success) {
logger.error({ errors: result.error.flatten() }, 'Environment validation failed');
throw result.error;
}

logger.info(
{ env: result.data.NODE_ENV, port: result.data.PORT },
'Configuration loaded successfully'
);

return result.data;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The loadConfig function is called in multiple places (app.ts, rateLimiter.ts), causing environment variables to be parsed and validated multiple times on startup. This is inefficient. You should ensure the configuration is loaded only once and shared as a singleton. A simple way to achieve this is to cache the configuration after the first load.

let loadedConfig: Env | undefined;

export function loadConfig(): Env {
  if (loadedConfig) {
    return loadedConfig;
  }

  const result = envSchema.safeParse(process.env);

  if (!result.success) {
    logger.error({ errors: result.error.flatten() }, 'Environment validation failed');
    throw result.error;
  }

  logger.info(
    { env: result.data.NODE_ENV, port: result.data.PORT },
    'Configuration loaded successfully'
  );

  loadedConfig = result.data;
  return loadedConfig;
}

Comment on lines +10 to +36
export async function connectMongo(uri: string): Promise<void> {
const maxRetries = 5;
const retryDelayMs = 2000;

for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await mongoose.connect(uri);
logger.info('MongoDB connected successfully');

mongoose.connection.on('error', (err) => {
logger.error({ err }, 'MongoDB connection error');
});

mongoose.connection.on('disconnected', () => {
logger.warn('MongoDB disconnected');
});

return;
} catch (err) {
logger.warn({ attempt, maxRetries, err }, 'MongoDB connection failed, retrying...');
if (attempt === maxRetries) {
throw err;
}
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The Mongoose connection event listeners (error, disconnected) are attached inside the connection retry loop. Since mongoose.connection is a singleton event emitter, this could lead to multiple listeners being attached if the connection logic were ever to be called more than once. It's more robust to attach these listeners once, outside of the connection loop.

export async function connectMongo(uri: string): Promise<void> {
  mongoose.connection.on('error', (err) => {
    logger.error({ err }, 'MongoDB connection error');
  });

  mongoose.connection.on('disconnected', () => {
    logger.warn('MongoDB disconnected');
  });

  const maxRetries = 5;
  const retryDelayMs = 2000;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await mongoose.connect(uri);
      logger.info('MongoDB connected successfully');
      return;
    } catch (err) {
      logger.warn({ attempt, maxRetries, err }, 'MongoDB connection failed, retrying...');
      if (attempt === maxRetries) {
        throw err;
      }
      await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
    }
  }
}

import { logger } from '../../utils/logger.js';

/** Mongoose connection state: 1 = connected */
const CONNECTED_STATE = 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

You are using the magic number 1 to represent MongoDB's connected state. For better readability and to avoid potential issues if the state values change in future Mongoose versions, it's recommended to use the ConnectionStates enum provided by Mongoose. You'll need to update the import on line 2 to import mongoose, { ConnectionStates } from 'mongoose';.

Suggested change
const CONNECTED_STATE = 1;
const CONNECTED_STATE = ConnectionStates.connected;

@LouisLetcher LouisLetcher deleted the cursor/cloud-native-mean-boilerplate-0dc4 branch February 20, 2026 10:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants