Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Pragyat-Nikunj/Learning-Management-System-backend/llms.txt

Use this file to discover all available pages before exploring further.

The LMS Backend applies several security controls globally in index.js before any request reaches a route handler. These are not opt-in per endpoint — they run on the entire application. As an API consumer you need to be aware of these controls because they shape which requests succeed, which are rejected, and how your client must be configured to communicate with the API correctly.

Security middleware stack

The controls are applied in this order at startup:
index.js
app.use(helmet());
app.use(hpp());
app.use((req, res, next) => {
    req.body = sanitize(req.body);
    req.params = sanitize(req.params);
    next();
});
app.use('/api', limiter);

Helmet

Sets a collection of HTTP response headers that protect against common web vulnerabilities: X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security, Content-Security-Policy, and others. Applied to every response without configuration.

HPP

HTTP Parameter Pollution protection. When duplicate query string parameters are submitted (e.g. ?role=student&role=admin), HPP picks the last value and removes duplicates so controllers always receive a scalar, not an array.

Mongo Sanitize

Strips MongoDB operator keys (those starting with $) from req.body and req.params before they reach any controller. This prevents NoSQL injection attacks where an attacker might submit { "email": { "$gt": "" } } to bypass authentication.

Rate Limiting

Limits each IP address to 100 requests per 15-minute window across all /api/* routes. Exceeding the limit returns a plain-text error message rather than a JSON response.

Rate limiting

The limiter is configured as follows and mounted on /api:
index.js
const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, //15 minutes
    limit: 100, //limit each IP to 100 request per 'window' (here, per 15 minutes)
    message: "Too many request from this IP, please try later"
});

app.use('/api', limiter);
When the limit is reached, the server responds with HTTP 429 Too Many Requests and the plain-text message "Too many request from this IP, please try later". The window resets 15 minutes after the first request in that window.
The rate limit applies per IP address and covers all /api/* routes — including /api/v1/healthcheck. If you are running automated tests or scripts that hit the API in a tight loop, you may hit this limit. Space requests out or run tests against a local development instance.

CORS policy

Cross-Origin Resource Sharing is configured to allow requests only from a specific origin. The allowed origin is read from the CLIENT_URL environment variable, defaulting to http://localhost:5173:
index.js
app.use(cors({
    origin: process.env.CLIENT_URL || "http://localhost:5173",
    credentials: true,
    methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
    allowedHeaders: [
        "Content-Type",
        "Authorization",
        "X-Requested-With",
        "device-remember-token",
        "Access-Control-Allow-Origin",
        "Origin",
        "Accept",
    ]
}));
Key points for client developers:
  • credentials: true is required on the server side so that the token cookie is included in cross-origin responses. Your client must also set credentials: "include" on every fetch call (or withCredentials: true in Axios) to send and receive cookies cross-origin.
  • Only the origin set in CLIENT_URL is allowed. Requests from any other origin will be blocked at the preflight stage.
  • The full allowed method set is GET, POST, PUT, DELETE, PATCH, HEAD, and OPTIONS.
If you are developing a frontend locally and the API is also local, ensure your frontend dev server runs on http://localhost:5173 (the Vite default) or update CLIENT_URL in the server’s .env to match your actual frontend origin.

Body size limits

To reduce the risk of request body attacks and memory exhaustion, JSON and URL-encoded request bodies are capped at 10 kb:
index.js
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));
Requests with bodies larger than 10 kb will be rejected with a 413 Payload Too Large error before reaching any controller. This limit applies to JSON and form-encoded bodies — it does not apply to file uploads, which are handled separately by Multer.

Stripe webhook raw body parsing

The Stripe webhook endpoint requires the raw, unmodified request body for signature verification. A special raw body parser is mounted before the global JSON parser to intercept only that path:
index.js
app.use("/api/v1/payments/webhook",
    express.raw({ type: "application/json" })  // Stripe needs raw body here
);
This means the route at POST /api/v1/payments/webhook receives a Buffer on req.body instead of a parsed object. Do not send manually crafted JSON to this endpoint — it is intended solely for Stripe’s webhook delivery system.
The order of middleware registration in index.js matters. The raw body parser for /api/v1/payments/webhook is mounted before express.json() so that Express does not parse the body before Stripe can verify its signature.

Security controls summary

ControlScopeBehaviour
HelmetAll routesSets secure HTTP response headers
HPPAll routesCollapses duplicate query parameters to the last value
Mongo SanitizeAll routesStrips $-prefixed keys from req.body and req.params
Rate limiter/api/*100 requests per IP per 15-minute window; returns 429 on breach
CORSAll routesRestricts origin to CLIENT_URL; requires credentials: include
JSON body limitAll routes except webhookRejects bodies larger than 10 kb
URL-encoded body limitAll routes except webhookRejects bodies larger than 10 kb
Raw body parser/api/v1/payments/webhook onlyPasses raw Buffer for Stripe signature verification

Build docs developers (and LLMs) love