Skip to content
Merged
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
33 changes: 33 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Application
NODE_ENV=development
PORT=4040

# Database (passed to schema initMongo as full URI)
MONGO_HOST=127.0.0.1
MONGO_PORT=27017
MONGO_DB=app
# MONGO_URI=mongodb://127.0.0.1:27017/app
MONGOOSE_DEBUG=false

# Auth
JWT_SECRET=change-me-to-a-long-random-secret-at-least-32-chars
JWT_EXPIRATION=24h

# Security
CORS_ORIGINS=http://localhost:3000
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX=100
AUTH_RATE_LIMIT_MAX=20
BODY_LIMIT=1mb
TRUST_PROXY=false
METRICS_ENABLED=true

# Logging
LOG_LEVEL=info

# Optional: email provider (for future password reset / verification modules)
# SMTP_HOST=
# SMTP_PORT=587
# SMTP_USER=
# SMTP_PASS=
# EMAIL_FROM=noreply@example.com
35 changes: 11 additions & 24 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,29 +1,16 @@
{
"parser": "babel-eslint",
"env": {
"node": true,
"es2022": true,
"jest": true
},
"extends": ["eslint:recommended", "prettier"],
"parserOptions": {
"ecmaVersion": 11
"ecmaVersion": 2022
},
"extends": [
"eslint:recommended",
"eslint-config-prettier",
"plugin:jsdoc/recommended"
],
"rules": {
"object-curly-spacing": [
"error",
"always"
],
"quotes": [
2,
"single",
{
"avoidEscape": true
}
]
},
"env": {
"node": true,
"amd": true,
"es6": true
"object-curly-spacing": ["error", "always"],
"quotes": ["error", "single", { "avoidEscape": true }],
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
}
}
}
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: npm
directory: '/'
schedule:
interval: weekly
open-pull-requests-limit: 10

- package-ecosystem: github-actions
directory: '/'
schedule:
interval: weekly
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: CI

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

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: npm

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Test
run: npm test
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,5 @@ node_modules
# .env
.env

package-lock.json

#storage
uploads
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# #!/usr/bin/env sh
# . "$(dirname -- "$0")/_/husky.sh"

npx lint-staged
5 changes: 2 additions & 3 deletions .lintstagedrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"**/*.+(js|jsx|json|yml|yaml|css|less|scss|ts|tsx)": [
"**/*.+(js|json|md|yml|yaml)": [
"prettier --write",
"eslint --fix",
"git add"
"eslint --fix"
]
}
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
61 changes: 61 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Contributing

## Adding a new API module

Follow the existing user module pattern:

```
server/<module>/
<module>.routes.js # HTTP routes + validation
<module>.controller.js # Request/response only
<module>.service.js # Business logic and authorization
<module>.helper.js # Data access via @applicationSchema/schemas
<module>.validation.js # Joi schemas
```

Wire routes in `index.route.js` under `/site/v1`.

## Schema boilerplate

Models and DB connection live in `../Boilerplate-application-schema` (`@applicationSchema/schemas`).

Before running locally:

1. Ensure the schema repo is a sibling folder (or update the `file:` path in `package.json`)
2. Run `npm install` to link the schema package
3. Add new models to the schema `index.js` exports, then import them in helpers here

## Response format

All API routes must use helpers from `utils/response.js`. See README for the envelope shape.

## Secrets management

**Local development with `.env`**
```bash
cp .env.example .env
npm run start:local
```

**Doppler (optional)**
```bash
doppler run -- npm run start:local
```

## Before opening a PR

```bash
npm run lint
npm test
```

Pre-commit hooks run lint-staged automatically via Husky.

## Planned modules (not yet implemented)

These require schema support first:

- Password reset flow (`User.findByEmailForPassReset` in schema)
- Email verification (verification token + email provider)

Add corresponding schema support first, then implement service/routes here.
17 changes: 17 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM node:20-alpine AS base

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY . .

ENV NODE_ENV=production
EXPOSE 4040

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://127.0.0.1:4040/health || exit 1

USER node
CMD ["node", "index.js"]
139 changes: 139 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Express + Mongoose Production Boilerplate

Production-oriented Node.js API using Express, wired to the **schema boilerplate** at `../Boilerplate-application-schema` (`@applicationSchema/schemas` — placeholder name).

## Quick start

```bash
cp .env.example .env
# Set JWT_SECRET and MongoDB values

npm install
npm run start:local
```

### Doppler (optional secrets)

```bash
doppler run -- npm run start:local
```

### Docker

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

API base path: `/site/v1`

## Schema boilerplate integration

This repo depends on your schema package via a local `file:` path:

```json
"@applicationSchema/schemas": "file:../Boilerplate-application-schema"
```

For a real app, rename the package in the schema repo (e.g. `@myAppSchema/schemas`) and update `package.json` + all `require()` paths here.

The schema package exports:

| Export | Purpose |
|--------|---------|
| `initMongo(mongoUri)` | Connect to MongoDB (throws on failure) |
| `disconnectMongo()` | Graceful disconnect |
| `isConnected()` | Connection status check |
| `USER_TYPES` | Shared user type constants |
| `User` | Mongoose User model (`findByCredentials`, `findByEmailForPassReset`) |

Usage in this repo:

```javascript
// index.js
const { initMongo } = require('@applicationSchema/schemas');
await initMongo(config.mongo.uri);

// server/user/user.helper.js
const { User } = require('@applicationSchema/schemas');
```

## Response format

**Success**
```json
{
"success": true,
"message": "User loaded",
"data": { "user": {} },
"meta": { "page": 1, "limit": 10, "total": 42, "pages": 5 }
}
```

**Error**
```json
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid credentials."
}
}
```

Every response includes an `X-Request-Id` header for log correlation.

## API endpoints

### Operational

| Method | Path | Description |
|--------|------|-------------|
| GET | `/health` | Liveness probe |
| GET | `/ready` | Readiness probe (MongoDB ping) |
| GET | `/metrics` | Prometheus metrics |

### Public (`/site/v1`)

| Method | Path | Description |
|--------|------|-------------|
| POST | `/user/register` | Register (`firstName`, `email`, strong `password`) |
| POST | `/user/login` | Login via `User.findByCredentials` |
| POST | `/user/logout` | Logout (client discards token) |

### Protected (Bearer token)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/user-me` | Current user profile |
| GET | `/users` | List users (Admin/Superuser only) |
| GET | `/user/:userID` | Get user (`U-XXXXXXXXXX` format) |
| PUT | `/user/:userID` | Update user (owner or admin) |
| DELETE | `/user/:userID` | Delete user (owner or admin) |

JWT payload includes `user_id`, `email`, and `userType` (`U100` Admin, `U200` Superuser, etc.).

## Project structure

```
├── config/ # Config, Express setup, constants
├── server/user/ # Routes → controller → service → helper
├── tests/ # Jest + Supertest (schema mocked in tests)
├── utils/ # Logger, response helpers, metrics
└── index.js # initMongo + graceful shutdown
```

## Scripts

| Script | Description |
|--------|-------------|
| `npm start` | Production |
| `npm run start:local` | Nodemon dev server |
| `npm test` | Run tests |
| `npm run lint` | ESLint |

See [CONTRIBUTING.md](CONTRIBUTING.md) for module conventions.

## License

MIT
Loading
Loading