All skills

Enforces professional Node.js/Express backend architecture — API design, database modeling, security, auth, and production-readiness standards. TRIGGER when: user builds or modifies any backend route, API endpoint, database schema, auth system, middleware, or server configuration. Also trigger when user asks about JWT, CORS, MongoDB, SQL, environment variables, deployment security, or backend file structure. TRIGGER also when: user's backend is crashing, leaking errors, or has security gaps they want to fix. DO NOT TRIGGER when: user is working on frontend components, CSS, React state, or any client-side UI task with no backend involvement.

  • 1 file
  • 12.5 KB
  • Updated last month
  • GitHub

Use this Skill: https://skilld.dev/gh/mdzubair933/web-design-mastery-skills/backend-architecture

This session only. Nothing lands on disk.

SKILL.md

≈168 tokens always: the name and description. ≈3k when used: this file.

Backend Architecture

Enforces production-grade Node.js/Express backend patterns — clean MVC structure, secure auth, professional API design, and hardened deployment. Primary constraint: no backend feature is built without a defined security boundary, centralized error handling, and environment-variable-only secrets.

Core Rules

Rule: All secrets live in .env only — never in source code. Why: automated bots scan public repositories 24/7; hardcoded credentials are stolen within minutes of exposure, causing data breaches and massive cloud billing spikes.

Rule: Never connect frontend directly to the database. Why: this bypasses all business logic, leaks connection strings to the browser, and removes the security layer entirely. All queries go through the Express backend only.

Rule: Wrap every controller in try-catch and use centralized error middleware. Why: unhandled exceptions crash the Node process silently — the server dies with no error returned to the client, and the cause is invisible.

Rule: Never store JWT access tokens in localStorage on the client. Why: XSS scripts read localStorage freely. Access tokens go in React state; refresh tokens go in HttpOnly cookies set by the backend — invisible to JS.

Rule: Whitelist only your production server IP in the database firewall. Why: open access (0.0.0.0/0) means any internet scanner can attempt connections. A single leaked credential becomes a full breach.

MVC Folder Structure (non-negotiable)

backend/
├── .env                    ← all secrets — never commit this
├── .gitignore              ← must include .env, node_modules/, dist/
└── src/
    ├── models/             ← Mongoose schemas or SQL table definitions
    ├── controllers/        ← business logic per resource (invoices, users, auth)
    ├── routes/             ← Express route declarations + HTTP method mapping
    ├── middleware/         ← auth checks, CORS, rate limiting, payload parsing
    └── services/           ← external utilities: PDF gen, email, Cron jobs

Each layer has one job:

  • models/ defines data shape — no logic.
  • controllers/ runs logic — no routing.
  • routes/ maps paths to controllers — no logic.
  • middleware/ intercepts requests — no business logic.
  • services/ handles external systems — no routing.

Never mix responsibilities across layers. Why: mixing makes bugs untraceable and makes the codebase unmaintainable as it grows.

API Design

REST Naming Rules:

  • Base: https://api.yourdomain.com
  • Versioning: always prefix with /api/v1/ — prevents breaking integrations when routes evolve.
  • Resources: plural nouns only (/users, /invoices, /orders)
  • Identifiers: parameterized (/:id) — never expose raw database keys in URLs

Standard Endpoint Map:

GET    /api/v1/invoices         → list all invoices for authenticated user
GET    /api/v1/invoices/:id     → get one invoice
POST   /api/v1/invoices         → create new invoice
PUT    /api/v1/invoices/:id     → replace entire invoice
PATCH  /api/v1/invoices/:id     → update specific fields only
DELETE /api/v1/invoices/:id     → permanently delete invoice

HTTP Methods — when to use which:

Method Use Behavior
GET Fetch data Safe, idempotent, no body
POST Create new record Body contains payload
PUT Replace entire resource Body replaces all fields
PATCH Update specific fields Body contains only changed fields
DELETE Remove record Irreversible unless soft-delete implemented

Status Codes — use exact codes, not just 200:

Code Meaning When to use
200 OK Successful GET, PUT, PATCH
201 Created Successful POST — new record written
400 Bad Request Invalid payload, missing required fields
401 Unauthorized No valid token or token expired
403 Forbidden Authenticated but lacks permission
404 Not Found Resource or route does not exist
409 Conflict Duplicate (email already registered)
500 Server Error Unhandled exception — should never reach client

Standard Response Wrapper:

// Success
{ "success": true, "data": { ... } }

// Error
{ "success": false, "errorCode": "INVOICE_NOT_FOUND", "message": "Invoice with this ID does not exist." }

Why: consistent wrappers allow the frontend to handle all responses with one pattern — checking success flag before accessing data.

Webhooks: Use for payment events (Razorpay, Stripe). The external system POSTs to your webhook URL on events — do not poll. Polling is slow, resource-heavy, and misses events during downtime.

Database Design

SQL vs NoSQL — decide using this rule:

Project type Database
Complex relationships, transactions, e-commerce PostgreSQL (SQL)
Flexible documents, user profiles, content-heavy MongoDB (NoSQL)
Default for AI-assisted fast iteration MongoDB + Mongoose

MongoDB Schema Rules:

  • Always define Mongoose schemas even though MongoDB is schema-less. Why: type-safety at the server layer prevents corrupt documents from being stored.
  • Required fields, type validation, and unique constraints must be declared in the schema — never rely on the application layer to enforce these.
  • Always index documents with userId to isolate user data. Why: queries without userId isolation will leak one user's data to another.
// Correct schema with type safety and isolation
const InvoiceSchema = new Schema({
  userId:    { type: Schema.Types.ObjectId, ref: "User", required: true, index: true },
  amount:    { type: Number, required: true },
  status:    { type: String, enum: ["draft", "sent", "paid"], default: "draft" },
  createdAt: { type: Date, default: Date.now },
});

Authentication & Security

Auth Architecture:

  • Register → hash password with bcrypt → store hash only, never plaintext.
  • Login → verify bcrypt hash → issue access token (short-lived, in JSON body)
    • refresh token (long-lived, in HttpOnly cookie).
  • Protected routes → verify access token in Authorization: Bearer header.
  • Token refresh → client sends HttpOnly cookie to /auth/refresh → new access token issued without re-login.

JWT Token Rules:

Token Storage Expiry Purpose
Access Token React state / Context 15 min Authorize API requests
Refresh Token HttpOnly cookie (backend sets) 7–30 days Mint new access tokens

CORS Configuration:

app.use(cors({
  origin: process.env.CLIENT_URL,  // never "*" in production
  credentials: true,               // required for HttpOnly cookies
}));

Why: wildcard CORS allows any domain to call your API — this defeats the purpose of having a backend security boundary.

Environment Variables — mandatory structure:

PORT=50001
MONGO_URI=mongodb+srv://...
JWT_ACCESS_SECRET=<random 64-char string>
JWT_REFRESH_SECRET=<random 64-char string>
CLIENT_URL=https://app.yourdomain.com

All of these go in .env only. They are set in the hosting provider's dashboard (Kloudbean) for production — never uploaded as a file.

Centralized Error Handling

Every controller must follow this pattern:

export const getInvoices = async (req, res, next) => {
  try {
    const invoices = await Invoice.find({ userId: req.user.id });
    res.status(200).json({ success: true, data: invoices });
  } catch (err) {
    next(err);  // passes to global error middleware — never call res.json in catch
  }
};

Global error middleware (registered last in app.js):

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    success: false,
    errorCode: err.code || "SERVER_ERROR",
    message: err.message || "An unexpected error occurred.",
  });
});

Why: without this, Node.js crashes silently on unhandled errors, leaving the client with no response and the developer with no traceable cause.

Automated Maintenance (Cron Jobs)

Use node-cron for background tasks that must run on a schedule:

  • Daily trash purge: delete records soft-deleted more than 30 days ago.
  • Token sweeper: delete expired password reset tokens from the database.
cron.schedule("0 0 * * *", async () => {
  await Trash.deleteMany({ deletedAt: { $lt: new Date(Date.now() - 30 * 86400000) } });
});

Production Readiness Checklist

Before any backend goes live, every item must be checked:

  • .env is in .gitignore — confirm with git status before pushing
  • All passwords hashed with bcrypt — no plaintext stored anywhere
  • Global error middleware registered and returning JSON (not HTML errors)
  • MongoDB Atlas IP whitelist contains only production server IP — not 0.0.0.0/0
  • CORS origin set to exact production domain — not *
  • All env variables set in hosting dashboard — not uploaded as a file
  • Cron jobs running for scheduled maintenance tasks
  • Postman collection created and all endpoints verified locally

Decision Guide

Situation Correct action
Unsure SQL vs MongoDB Ask: complex multi-table joins needed? Yes → SQL. No → MongoDB
Need to update one field only Use PATCH, not PUT
Frontend getting 500 errors Add centralized error middleware — server is crashing silently
API works locally but fails in production Check: CORS origin, env vars in hosting dashboard, DB IP whitelist
User data leaking across accounts Add userId filter to all queries — missing isolation

Anti-Patterns

❌ Never do this ✅ Do this instead
Hardcode secrets in source files Store all secrets in .env only
Leave DB open to 0.0.0.0/0 Whitelist production server IP only
Use * as CORS origin Set exact domain via process.env.CLIENT_URL
Store plaintext passwords Always hash with bcrypt before storing
Mix logic across MVC layers One layer, one job — strictly separated
Return raw error stack traces Use centralized middleware returning structured JSON
Poll for payment events Use webhooks — trigger-driven, real-time, no overhead

Gotchas

  • A backend that works perfectly locally but crashes in production is almost always missing environment variables in the hosting dashboard. Check this first before debugging anything else.

  • MongoDB Mongoose validation only runs on save() and create() — not on findOneAndUpdate() unless you pass { runValidators: true }. Missing this allows corrupt data to bypass schema constraints.

  • HttpOnly cookies require credentials: true in both the CORS config and the frontend fetch call. Missing either side and the cookie is never sent.

  • JWT process.env.JWT_ACCESS_SECRET that is undefined will cause the jwt.sign() call to fail silently or sign with "undefined" as the secret — always check env vars load before the app starts.


Handoff

Skill: backend-architecture
Type: Reference
Trigger phrases covered: "build an API route", "set up auth", "JWT refresh token",
  "MongoDB schema", "backend is crashing", "CORS error"
DO NOT TRIGGER for: frontend components, CSS, React state, UI layout tasks
Test prompts to verify:
  1. "Set up JWT authentication with refresh tokens" → Should trigger; HttpOnly cookie
     pattern, access token in React state, bcrypt hashing — all enforced
  2. "My backend is returning 500 errors in production but works locally"
     → Should trigger; env vars in hosting dashboard checked, CORS origin verified
  3. "Build me a Tailwind card component" → Should NOT trigger; frontend UI task
Pass/fail rubric score: 5/5
Suggested next iteration: add SQL/PostgreSQL specific schema and query patterns
  if user's projects shift toward relational data models

Source: SKILL.md on GitHub

No third-party reports yet.

Signed by skilld at e0e2d0a. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated last month

README badge

README badge for mdzubair933/web-design-mastery-skills/backend-architecture