Skip to content
Closed
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
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
21 changes: 21 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ==============================================
# MEAN Cloud-Native Boilerplate — Environment
# ==============================================
# Copy this file to `.env` and fill in real values.

# --- MongoDB ---
MONGO_PORT=27017
MONGO_USERNAME=admin
MONGO_PASSWORD=password
MONGO_DATABASE=mean_app

# --- Backend ---
BACKEND_PORT=3000
NODE_ENV=development
JWT_SECRET=change-me-to-a-random-string
JWT_EXPIRATION=1h
CORS_ORIGIN=http://localhost:4200
LOG_LEVEL=debug

# --- Frontend ---
FRONTEND_PORT=4200
83 changes: 83 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: CI Pipeline

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

env:
NODE_VERSION: '20'

jobs:
backend:
name: Backend — Lint, Test & Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: backend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Type check
run: npx tsc --noEmit

- name: Test
run: npm test

- name: Build
run: npm run build

frontend:
name: Frontend — Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Build
run: npx ng build --configuration production

docker:
name: Docker Build Verification
runs-on: ubuntu-latest
needs: [backend, frontend]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Build backend Docker image
run: docker build --target production -t mean-backend:ci ./backend

- name: Build frontend Docker image
run: docker build --target production -t mean-frontend:ci ./frontend
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
node_modules/
dist/
build/
coverage/
.env
.env.local
*.log
.DS_Store
.angular/
.vscode/
*.tgz
.nyc_output/
tmp/
1 change: 1 addition & 0 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx --no -- commitlint --edit $1
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
6 changes: 6 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
dist
build
coverage
.angular
.next
10 changes: 10 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"endOfLine": "lf",
"arrowParens": "always",
"bracketSpacing": true
}
139 changes: 138 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,138 @@
# mean-boilerplate
# Cloud-Native MEAN Stack Boilerplate

A production-ready, cloud-native MEAN (MongoDB, Express.js, Angular, Node.js) boilerplate built with **TypeScript** and following the **12-Factor App** methodology.

## Tech Stack

| Layer | Technology |
| ---------- | ---------------------------------------------- |
| Database | MongoDB 7 + Mongoose ODM |
| Backend | Node.js 20+ / Express.js / TypeScript (Strict) |
| Frontend | Angular 19 / Standalone Components / Signals |
| Containers | Docker multi-stage builds / Docker Compose |
| CI/CD | GitHub Actions |

## Project Structure

```
├── backend/
│ ├── src/
│ │ ├── config/ # Env validation (Zod), DB connection
│ │ ├── middleware/ # Error handler, validation, not-found
│ │ ├── modules/
│ │ │ ├── auth/ # JWT auth (register, login, middleware)
│ │ │ ├── health/ # Liveness & readiness probes
│ │ │ └── users/ # CRUD user management
│ │ ├── utils/ # Logger (Pino), AppError, response helpers
│ │ ├── app.ts # Express app factory
│ │ └── server.ts # Bootstrap & graceful shutdown
│ ├── tests/ # Jest unit & integration tests
│ └── Dockerfile # Multi-stage (dev + prod)
├── frontend/
│ ├── src/
│ │ ├── app/
│ │ │ ├── core/ # Auth service, interceptors, guards
│ │ │ ├── features/ # Home, Auth (login/register), Dashboard
│ │ │ └── shared/ # Models, shared components
│ │ ├── environments/ # Dev & prod environment configs
│ │ └── styles/ # Global SCSS
│ ├── nginx.conf # Production Nginx config
│ └── Dockerfile # Multi-stage (dev + Nginx prod)
├── .github/workflows/ci.yml # CI pipeline
├── docker-compose.yml # Full stack local development
└── .husky/ # Git hooks (commitlint, lint-staged)
```

## Quick Start

### Prerequisites

- Node.js >= 20
- Docker & Docker Compose
- npm >= 10

### Development with Docker (Recommended)

```bash
cp .env.example .env
docker compose up --build
```

| Service | URL |
| -------- | ------------------------- |
| Frontend | http://localhost:4200 |
| Backend | http://localhost:3000 |
| MongoDB | mongodb://localhost:27017 |

### Local Development (without Docker)

```bash
# Install all dependencies
npm install

# Start backend (requires MongoDB running)
npm run dev:backend

# Start frontend (in another terminal)
npm run dev:frontend
```

## API Endpoints

### Health Checks

| Method | Endpoint | Description |
| ------ | ------------------- | ------------------------------------ |
| GET | `/health/liveness` | Process is running |
| GET | `/health/readiness` | App can serve traffic (DB connected) |

### Auth

| Method | Endpoint | Description |
| ------ | -------------------- | ----------------- |
| POST | `/api/auth/register` | Register new user |
| POST | `/api/auth/login` | Login |
| GET | `/api/auth/profile` | Get current user |

### Users (Authenticated)

| Method | Endpoint | Description |
| ------ | ---------------- | ------------------ |
| GET | `/api/users` | List users (paged) |
| GET | `/api/users/:id` | Get user by ID |
| PATCH | `/api/users/:id` | Update user |
| DELETE | `/api/users/:id` | Soft-delete user |

## Testing

```bash
# Backend tests
cd backend && npm test

# Frontend tests (requires Chrome/Chromium)
cd frontend && npm test
```

## Code Quality

- **ESLint** + **Prettier** for consistent formatting
- **Husky** pre-commit hooks run lint-staged
- **Commitlint** enforces Conventional Commits (`feat:`, `fix:`, etc.)
- **TypeScript Strict Mode** across the entire stack

## Security

- **Helmet** for HTTP security headers
- **CORS** configured per environment
- **Rate limiting** on API routes (100 req / 15 min)
- **bcrypt** password hashing (12 salt rounds)
- **JWT** bearer token authentication
- **Zod** request validation

## Environment Variables

See `.env.example` for all configuration options. The backend validates all required environment variables at startup using Zod — the server will refuse to start with invalid config.

## License

MIT
7 changes: 7 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
dist
coverage
.env
.env.local
*.log
.git
18 changes: 18 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# ==============================================
# Backend Environment Variables
# ==============================================
NODE_ENV=development
PORT=3000

# --- MongoDB ---
MONGO_URI=mongodb://admin:password@localhost:27017/mean_app?authSource=admin

# --- JWT ---
JWT_SECRET=change-me-to-a-random-string
JWT_EXPIRATION=1h

# --- CORS ---
CORS_ORIGIN=http://localhost:4200

# --- Logging ---
LOG_LEVEL=debug
45 changes: 45 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ============================================
# Stage 1: Base image with dependencies
# ============================================
FROM node:20-alpine AS base
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --ignore-scripts
COPY . .

# ============================================
# Stage 2: Development (hot-reload with tsx)
# ============================================
FROM base AS development
ENV NODE_ENV=development
EXPOSE 3000
CMD ["npm", "run", "dev"]

# ============================================
# Stage 3: Build the TypeScript source
# ============================================
FROM base AS build
RUN npm run build
Comment on lines +4 to +22
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To improve Docker layer caching and optimize build times, it's recommended to separate the dependency installation from copying the application source code. The current base stage bundles them, causing the cache to be invalidated on any source code change.

By creating a dependencies stage that only handles npm ci, and then having development and build stages copy the source code, you ensure that dependencies are only re-installed when package.json or package-lock.json changes.

# ============================================
# Stage 1: Dependencies
# ============================================
FROM node:20-alpine AS dependencies
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --ignore-scripts

# ============================================
# Stage 2: Development (hot-reload with tsx)
# ============================================
FROM dependencies AS development
COPY . .
ENV NODE_ENV=development
EXPOSE 3000
CMD ["npm", "run", "dev"]

# ============================================
# Stage 3: Build the TypeScript source
# ============================================
FROM dependencies AS build
COPY . .
RUN npm run build


# ============================================
# Stage 4: Production-optimized image
# ============================================
FROM node:20-alpine AS production
WORKDIR /app
ENV NODE_ENV=production

RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup

COPY package.json package-lock.json* ./
RUN npm ci --omit=dev --ignore-scripts && npm cache clean --force

COPY --from=build /app/dist ./dist

USER appuser
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:3000/health/liveness || exit 1

CMD ["node", "dist/server.js"]
Loading