Cpeak is a minimal and fast Node.js framework inspired by Express.js & Fastify.
This project is designed to be improved until it's ready for use in complex production applications, aiming to be more performant and have a cleaner structure than Express.js and other common frameworks.
Cpeak replaces Express.js, body-parser, cookie-parser, compression, CORS, Passport and more, in one zero-dependency package. You can use it as a drop-in replacement for Express.js, and many npm packages that work with Express.js will also work with Cpeak.
Cpeak currently has almost all the features of Express.js, with roughly the same performance as Fastify. Additionally, it has a codebase that you can easily navigate, audit and change if you want. Benchmarks and test results, and videos on navigating and understanding the codebase will be added soon.
Cpeak started as an educational project, and now it's being used for production. At Cododev we build real products, get real users, and then use them to offer a unique kind of education for the age of AI. To see how we teach, and some of the products built with Cpeak, visit cododev.ca.
- Minimalism: No unnecessary bloat, with zero dependencies. Just the core essentials you need to build fast and reliable applications.
- Performance: Engineered to be fast, Cpeak won’t sacrifice speed for excessive customizability.
- Educational: Every new change made in the project will be explained in great detail in this YouTube playlist. Follow this project and let's see what it takes to build an industry-leading product!
- Express.js Compatible: You can easily refactor from Cpeak to Express.js and vice versa. Many npm packages that work with Express.js will also work with Cpeak.
Ready to dive in? Install Cpeak via npm:
npm install cpeakCpeak is a pure ESM package, and to use it, your project needs to be an ESM as well. You can learn more about that here.
import cpeak from "cpeak";
const server = cpeak();
server.route("get", "/", (req, res) => {
return res.json({ message: "Hi there!" });
});
server.listen(3000, () => {
console.log("Server has started on port 3000");
});Include the framework like this:
import cpeak from "cpeak";Because of the minimalistic philosophy, you won’t add unnecessary objects to your memory as soon as you include the framework. If at any point you want to use a particular utility function (like parseJSON and serveStatic), include it like the line below, and only at that point will it be moved into memory:
import cpeak, { serveStatic, parseJSON } from "cpeak";Initialize the Cpeak server like this:
const server = cpeak();Now you can use this server object to start listening, add route logic, add middleware functions, and handle errors.
If you add a middleware function, that function will run before your route logic kicks in. Here you can customize the request object, return an error, or do anything else you want to do prior to your route logic, like authentication.
After calling next, the next middleware function is going to run if there’s any; otherwise, the route logic is going to run.
server.beforeEach((req, res, next) => {
if (req.headers.authentication) {
// Your authentication logic...
req.userId = "<something>";
req.custom = "This is some string";
next();
} else {
// Return an error and close the request...
return res.status(401).json({ error: "Unauthorized" });
}
});
server.beforeEach((req, res, next) => {
console.log(
"The custom value was added from the previous middleware: ",
req.custom
);
next();
});You can also add middleware functions for a particular route handler like this:
const requireAuth = (req, res, next) => {
// Check if user is logged in, if so then:
req.test = "this is a test value";
next();
// If user is not logged in:
throw { status: 401, message: "Unauthorized" };
};
server.route("get", "/profile", requireAuth, (req, res) => {
console.log(req.test); // this is a test value
});You can add as many middleware functions as you want for a route:
server.route(
"get",
"/profile",
requireAuth,
anotherFunction,
oneMore,
(req, res) => {
// your logic
}
);You can add new routes like this:
server.route("patch", "/the-path-you-want", (req, res) => {
// your route logic
});First add the HTTP method name you want to handle, then the path, and finally, the callback. The req and res object types are the same as in the Node.js HTTP module (http.IncomingMessage and http.ServerResponse). You can read more about them in the official Node.js documentation.
Under the hood, Cpeak stores your routes in a radix tree, so finding the right route for an incoming request is roughly O(log n) in your total route count. In plain terms, that means matching stays fast even when your app has hundreds or thousands of routes. You won't pay a linear scan per request as your route table grows.
Cpeak is written in TypeScript. CpeakRequest and CpeakResponse both accept generics, so you can type a route's request body, query string, and response body without casting inside the handler:
import cpeak, { CpeakRequest, CpeakResponse } from "cpeak";
interface CreatePostBody {
title: string;
content: string;
}
interface CreatePostResponse {
id: number;
title: string;
}
const server = cpeak();
server.route(
"post",
"/posts",
async (
req: CpeakRequest<CreatePostBody>,
res: CpeakResponse<CreatePostResponse>
) => {
const { title, content } = req.body ?? {};
if (!title) throw { status: 400, message: "title is required" };
const post = await db.createPost({ title, content });
return res.json({ id: post.id, title: post.title }); // checked against CreatePostResponse
}
);A few things worth knowing:
req.bodycan beundefined. If a request doesn't come withContent-Type: application/json, there's no body to read. So grab your fields fromreq.body ?? {}like above. TypeScript then knows each field might be missing:titleabove isstring | undefined, notstring.- The second generic is for
req.query, notreq.params.CpeakRequest<ReqBody, ReqQueries>types the body and the query string. Route params like/:idare always strings, and there's no way to type them yet.
The same generics are available on Middleware<ReqBody, ReqQueries, ResBody> for server.beforeEach and route-level middleware.
If no route or middleware matches an incoming request, Cpeak returns a default 404 message. You can replace it with your own handler using server.fallback:
server.fallback((req, res) => {
return res.status(404).json({ error: "not found" });
});Use this for custom 404 pages, JSON error envelopes, or serving an SPA's index.html for unknown paths. Static, param, and * wildcard routes still win when they match, the fallback only fires once the router has no match.
To be more consistent with the broader Node.js community and frameworks, we call the HTTP URL parameters (query strings) 'query', and the path variables (route parameters) 'params'.
Here’s how we can read both:
// Imagine request URL is example.com/test/my-title/more-text?filter=newest
server.route("patch", "/test/:title/more-text", (req, res) => {
const title = req.params.title;
const filter = req.query.filter;
console.log(title); // my-title
console.log(filter); // newest
});You can send a file as a Node.js Stream anywhere in your route or middleware logic like this:
server.route("get", "/testing", (req, res) => {
return res.status(200).sendFile("<file-path>", "<mime-type>");
// Example:
// return res.status(200).sendFile("./images/sun.jpeg", "image/jpeg");
});The file’s binary content will be in the HTTP response body content. Make sure you specify a correct path relative to your CWD (use the path module for better compatibility).
The MIME type argument is optional. When omitted, Cpeak infers it from the file extension using its built-in MIME registry (see MIME Types):
return res.status(200).sendFile("./images/sun.jpeg");Passing the MIME type explicitly is the fastest path, Cpeak skips the extension lookup entirely.
If you want to redirect to a new URL, you can simply do:
res.redirect("https://whatever.com");serveStatic, res.sendFile, and res.render all share a single MIME type registry. Out of the box it covers the common web types:
html: "text/html",
css: "text/css",
js: "application/javascript",
jpg: "image/jpeg",
jpeg: "image/jpeg",
png: "image/png",
svg: "image/svg+xml",
gif: "image/gif",
ico: "image/x-icon",
txt: "text/plain",
json: "application/json",
webmanifest: "application/manifest+json",
eot: "application/vnd.ms-fontobject",
otf: "font/otf",
ttf: "font/ttf",
woff: "font/woff",
woff2: "font/woff2"
If you need to serve something else, register it once on the cpeak() constructor:
const server = cpeak({
mimeTypes: { mp3: "audio/mpeg", m4a: "audio/mp4" }
});We keep this list small on purpose. Cpeak isn't going to load the thousands of MIME types from the IANA registry into your process memory on the off chance you might serve an .apk or a .fla one day. You tell us what you serve, we keep memory tight.
You can enable HTTP response compression at construction time. Once enabled, serveStatic, res.json() and res.sendFile() will compress eligible responses automatically, and you also get a res.compress() method on the response for custom payloads.
Fire it up with the defaults like this:
const server = cpeak({ compression: true });Or pass options to tune the behavior:
const server = cpeak({
compression: {
threshold: 1024, // bytes — responses smaller than this are sent uncompressed. Default: 1024
brotli: {}, // node:zlib BrotliOptions
gzip: {}, // node:zlib ZlibOptions
deflate: {} // node:zlib ZlibOptions
}
});For arbitrary payloads, like a Buffer, string, or Readable stream, use res.compress:
server.route("get", "/report", async (req, res) => {
const csv = await buildCsvReport();
await res.compress("text/csv", csv);
});When you're streaming, you can pass a known size as the third argument. Cpeak will use it to decide eligibility against threshold, and to set Content-Length if the body ends up being sent uncompressed:
import { Readable } from "node:stream";
server.route("get", "/proxy/feed", async (req, res) => {
const upstream = await fetch("https://example.com/feed.xml");
const size = Number(upstream.headers.get("content-length"));
await res.compress("application/xml", Readable.fromWeb(upstream.body), size);
});You must first enable compression at construction time to use res.compress.
If anywhere in your route functions or route middleware functions you want to return an error, you can just throw the error and let the automatic error handler catch it:
server.route("get", "/api/document/:title", (req, res) => {
const title = req.params.title;
if (title.length > 500) throw { status: 400, message: "Title too long." };
// The rest of your logic...
});Make sure to call server.handleErr and pass a function like this to have the automatic error handler work properly:
server.handleErr((error, req, res) => {
if (error && error.status) {
return res.status(error.status).json({ error: error.message });
} else {
// Log the unexpected errors somewhere so you can keep track of them...
console.error(error);
return res.status(500).json({
error: "Sorry, something unexpected happened on our side."
});
}
});The error object is the object that you threw earlier in your routes or middleware.
One thing to keep in mind: res.sendFile, res.render, res.compress, and res.json all return a Promise. Return that promise (or await it in an async handler) so the framework can route any rejection to your handleErr:
server.route("get", "/file", (req, res) => {
return res.sendFile("./images/sun.jpeg", "image/jpeg");
});Or, in an async handler that does other work alongside the send:
server.route("get", "/avatars/:id", async (req, res) => {
const path = await db.lookupAvatarPath(req.params.id);
await res.sendFile(path);
});log() writes a line of your own, and the log.requests() middleware writes a line for every request:
import cpeak, { log } from "cpeak";
const server = cpeak();
server.beforeEach(log.requests());
server.route("get", "/", (req, res) => {
log("Someone said hi");
return res.json({ message: "Hi there!" });
});
server.listen(3000, () => {
log("Server has started on port 3000");
});The lines go to stdout and look like this:
2026-10-15 14:00:00 -- Server has started on port 3000
2026-10-15 14:00:02 -- Someone said hi
2026-10-15 14:00:02 -- ::ffff:127.0.0.1 -- GET / 200 OK -- 0.21 ms
Like any middleware, where you put log.requests() decides which requests get a line. The ones that end before they reach it don't, so to leave out the files that serveStatic() sends, put it after that:
server.beforeEach(serveStatic("./public")); // these never reach the log
server.beforeEach(log.requests());Or put it in front of everything to log them all. It also works on a single route, like server.route("get", "/api/orders", log.requests(), handler). The time in a line is from when the request came in, not from when it reached log.requests().
To log an error, pass it to log(). It goes to the log of errors (stderr by default), with its name, its message and its stack trace. Your handleErr is a good place for it:
server.handleErr((error, req, res) => {
if (error && error.status) {
return res.status(error.status).json({ error: error.message });
}
log(error);
return res.status(500).json({
error: "Sorry, something unexpected happened on our side."
});
});Where the lines go is up to the log option of your app. Here it is with everything in it, and what each one is when you leave it out:
const server = cpeak({
log: {
// Where log("...") and the request lines go. Default: "stdout"
// A {day} in the path starts a new file every day, and the folders are made for you.
output: { file: "logs/{day}/info.log" },
// Where log(error) goes. Default: "stderr"
errors: { file: "logs/{day}/errors.log" },
// The clock of the dates (and of when a {day} file starts). Default: "utc"
timeZone: "local",
// The lines are collected and written out together once there are this many bytes,
// or once this many milliseconds have passed. Defaults: 64KB and 100ms
bufferSize: 64 * 1024,
flushEvery: 100
}
});And these are the options of log.requests():
server.beforeEach(
log.requests({
format: "{date} -- {ip} -- {method} {url} {status} {status_message} -- {ms} ms",
// Behind a proxy or a load balancer, read the IP of the client from this header
// (when it has a value). Default: the address of the connection
ipHeader: "x-forwarded-for",
// Which requests get a line. Default: all of them. With more than one of these
// set, a request has to pass every one.
onlyErrors: false, // only a status of 400 and above
slowerThan: 500, // only the slow ones (milliseconds)
sample: 100, // only 1 in 100
skip: (req, res) => req.url === "/health" // your own rule
})
);These are the names you can use in format:
| Name | What it writes |
|---|---|
{date} |
2026-10-15 14:00:00 |
{iso} |
2026-10-15T14:00:00.123Z (or -04:00 instead of the Z with timeZone: "local") |
{ip} |
the IP of the client |
{method} |
GET |
{url} |
the URL the request came in with, like /users?filter=active |
{path} |
the URL without the query string |
{status} |
302 |
{status_message} |
Found |
{ms} |
how long the request took, in milliseconds (0.21) |
{us} |
how long the request took, in microseconds (210) |
{bytes} |
the size of the body that was sent |
{header:NAME} |
any request header, like {header:user-agent} (- when it's missing) |
A name that isn't on the list throws when log.requests() is called, so a typo shows up right away.
The log is made to keep up with a busy server. The lines are collected in a buffer and written out together, and the writing happens off the event loop, so a request never waits for the disk. If the disk can't keep up, the lines that don't fit in line are dropped instead of piling up in memory, and the log says how many once it catches up. An error is written right away, and never dropped. What's left is written when the server closes, and when the program exits (with process.exit(), or once there's nothing left to do). A program that a signal stops without a handler for it (like SIGTERM, from a process manager) can lose the lines of its last flushEvery, so call process.exit() in your SIGTERM handler.
log() works anywhere, and goes where the log option of your app says. When no app has the option, the text goes to stdout and the errors to stderr.
Start listening on a specific port like this:
server.listen(3000, () => {
console.log("Server has started on port 3000");
});server.listen accepts the same arguments as Node.js's http.Server.listen, so you can also pass a host, an options object, or a Unix socket path.
There are utility functions that you can include and use as middleware functions. These are meant to make it easier for you to build HTTP applications. In the future, many more will be added, and you only move them into memory once you include them. No need to have many npm dependencies for simple applications!
The list of utility functions as of now:
- serveStatic
- parseJSON
- render
- cookieParser
- swagger
- auth
- cors
Including any one of them is done like this:
import cpeak, { utilName } from "cpeak";With this middleware function, you can automatically set a folder in your project to be served by Cpeak. Here’s how to set it up:
server.beforeEach(serveStatic("./public"));serveStatic infers each file's MIME type from its extension. If your folder contains types that aren't in the built-in registry, register them on the cpeak() constructor (see MIME Types).
You can also serve your static files under a URL prefix by passing a prefix option. This is useful when you want all static assets to live under a specific path like /static:
server.beforeEach(
serveStatic("./public", { prefix: "/static" })
);With this setup, a file at ./public/app.js would be served at /static/app.js instead of /app.js.
If file names in the served folder change while the server is running (e.g. during development), pass live: true so the middleware stats the disk on each request instead of caching the file map at startup:
server.beforeEach(serveStatic("./public", { live: true }));With this middleware function, you can easily read and send JSON in HTTP message bodies in route and middleware functions. Fire it up like this:
// You can pass an optional limit option to indicate the maximum
// JSON body size that your server will accept.
server.beforeEach(parseJSON({ limit: 1024 * 1024 })); // default value is 1024 * 1024 (1MB)Read and send JSON from HTTP messages like this:
server.route("put", "/api/user", (req, res) => {
// Reading JSON from the HTTP request:
const email = req.body.email;
// rest of your logic...
// Sending JSON in the HTTP response:
return res.status(201).json({ message: "Something was created..." });
});With this function you can do server side rendering before sending a file to a client. This can be useful for dynamic customization and search engine optimization.
First fire it up like this:
server.beforeEach(render());And then for rendering:
server.route("get", "/", (req, res, next) => {
return res.render(
"./public/index.html",
{
title: "Page title",
name: "Allan"
},
"text/html"
);
});The third argument (the MIME type) is optional. When omitted, Cpeak infers it from the file extension using the registry on the cpeak() constructor.
You can then inject the variables into your file in {{ variable_name }} like this:
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ name }}</h1>
</body>
</html>With this middleware function, you can easily read and set cookies in your route and middleware functions. Fire it up like this:
server.beforeEach(cookieParser());If you need to use signed cookies, pass a secret:
server.beforeEach(cookieParser({ secret: "your-secret-key" }));Signed cookies use HMAC to verify integrity. The original value stays readable by the client, but any tampering with it will be detected on the server side. This makes them a solid choice for session identifiers or user IDs where you want to prevent impersonation without hiding the value itself.
Read incoming cookies like this:
server.route("get", "/dashboard", (req, res) => {
// Regular cookies
const theme = req.cookies.theme;
// Signed cookies — returns false if the signature is invalid or the value was tampered with
const userId = req.signedCookies.userId;
return res.status(200).json({ theme, userId });
});Set cookies on the response like this:
server.route("post", "/login", (req, res) => {
// A plain cookie
res.cookie("theme", "dark", { httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000 });
// A signed cookie
res.cookie("userId", "abc123", { signed: true, httpOnly: true, secure: true });
return res.status(200).json({ message: "Logged in" });
});Clear a cookie like this:
res.clearCookie("userId");The full list of cookie options you can pass as the third argument to res.cookie():
signed— sign the cookie value with HMAC using the secret you provided tocookieParserhttpOnly— prevents client-side JavaScript from accessing the cookiesecure— instructs the browser to send the cookie only over HTTPSsameSite— controls cross-site cookie behavior; accepts"strict","lax", or"none"maxAge— cookie lifetime in millisecondsexpires— a specific expirationDatefor the cookiepath— path the cookie is valid for (defaults to"/")domain— domain the cookie is valid for
With this middleware function, you can serve an interactive Swagger UI for your API documentation. It works alongside the serveStatic utility and two npm packages: swagger-ui-dist (the Swagger UI static assets) and yamljs (to load your YAML spec file).
Start by installing the dependencies:
npm install swagger-ui-dist yamljsThen fire it up like this:
import cpeak, { swagger, serveStatic } from "cpeak";
import YAML from "yamljs";
import swaggerUiDist from "swagger-ui-dist";
import path from "node:path";
const server = cpeak();
const swaggerDocument = YAML.load(
path.join(path.resolve(), "./src/swagger.yml")
);
server.beforeEach(swagger(swaggerDocument));
server.beforeEach(
serveStatic(swaggerUiDist.getAbsoluteFSPath(), undefined, {
prefix: "/api-docs",
})
);Once set up, your Swagger UI will be available at /api-docs. The swagger middleware handles serving your spec at /api-docs/spec.json and wiring up the Swagger UI initializer, while serveStatic serves all the Swagger UI static assets under the same prefix.
If you want to serve the docs under a different path, pass it as the second argument to swagger and match the prefix in serveStatic:
server.beforeEach(swagger(swaggerDocument, "/docs"));
server.beforeEach(
serveStatic(swaggerUiDist.getAbsoluteFSPath(), undefined, {
prefix: "/docs",
})
);With this middleware you can add a full-fledged authentication system to your application with emails, username and password authentication, with features such as Forgot Password, Update Password and so forth. We have no external dependencies, with timing-safe comparisons throughout. It attaches helper methods directly to req so your route handlers stay clean.
Fire it up like this:
import cpeak, { parseJSON, cookieParser, auth } from "cpeak";
const app = cpeak();
app.beforeEach(parseJSON());
app.beforeEach(cookieParser());
app.beforeEach(
auth({
// Required
secret: "your-secret-min-32-chars-long!!!", // used to sign token IDs with HMAC
saveToken: async (tokenId, userId, expiresAt) => { /* store in your DB */ },
findToken: async (tokenId) => { /* return { userId, expiresAt } or null */ },
// Enables req.logout()
revokeToken: async (tokenId) => { /* delete from your DB */ },
// Optional PBKDF2 tuning (defaults shown):
iterations: 210_000, // higher = slower brute-force
keylen: 64, // derived key length in bytes
digest: "sha512",
saltSize: 32,
// Optional token signing tuning (defaults shown):
hmacAlgorithm: "sha256",
tokenIdSize: 20,
tokenExpiry: 7 * 24 * 60 * 60 * 1000, // 7 days in ms
})
);Once set up, the following methods are available on req inside your routes and middleware:
| Method | Description |
|---|---|
req.hashPassword({ password }) |
Hashes a password with PBKDF2. Store the result; never store plaintext. |
req.login({ password, hashedPassword, userId }) |
Verifies the password and if correct, creates a signed token. Returns the token string to send to the client, or null on wrong password. |
req.verifyToken(token) |
Validates a token's HMAC signature and expiry. Returns { userId } or null. |
req.logout(token) |
Revokes the token via your revokeToken callback. Only available when revokeToken is provided. |
Here are the two most common middleware patterns you'll want to set up:
// Throws 401 if the request has no valid token. Use on protected routes.
const requireAuth = async (req, res, next) => {
const token = req.headers["authorization"];
if (!token) throw { status: 401, message: "Unauthorized." };
const result = await req.verifyToken(token);
if (!result) throw { status: 401, message: "Unauthorized." };
req.user = { id: result.userId };
next();
};
// Silently sets req.user when a valid token is present, but lets the request through either way.
// Useful for routes accessible by both authenticated and unauthenticated users.
const optionalAuth = async (req, _res, next) => {
const token = req.headers["authorization"];
if (token) {
const result = await req.verifyToken(token);
if (result) req.user = { id: result.userId };
}
next();
};For complete working examples, see:
examples/auth-localstorage.js— token sent via theAuthorizationheader (suited for SPAs and mobile clients)examples/auth-cookies.js— token stored in anhttpOnlycookie (protects against XSS)
The CORS middleware allows you to enable Cross-Origin Resource Sharing in your application.
server.beforeEach(cors({
origin: "http://localhost:3000", // string, string[], RegExp, boolean, or async (origin) => boolean. Default: "*" (all origins)
methods: "GET,POST,PUT,DELETE", // allowed HTTP methods. Default: "GET,HEAD,PUT,PATCH,POST,DELETE"
allowedHeaders: "Content-Type", // headers the browser may send. Default: echoes request headers for origin:"*", else "Content-Type, Authorization"
exposedHeaders: "X-Request-Id", // response headers the browser may read. Default: none
credentials: true, // adds Access-Control-Allow-Credentials: true. Default: false
maxAge: 3600, // seconds to cache preflight result in the browser. Default: 86400
preflightContinue: false, // pass OPTIONS preflight to next middleware instead of auto-responding. Default: false
optionsSuccessStatus: 204 // status code for successful preflight responses. Default: 204
}));Or if you don't care and want to allow everything with the default settings, just do:
server.beforeEach(cors());Here you can see all the features that Cpeak offers (excluding the authentication features), in one small piece of code:
import cpeak, { serveStatic, parseJSON, render, cookieParser, cors } from "cpeak";
const server = cpeak({
mimeTypes: { mp3: "audio/mpeg" }
});
server.beforeEach(serveStatic("./public"));
server.beforeEach(render());
// For parsing JSON bodies
server.beforeEach(parseJSON());
// For reading and setting cookies
server.beforeEach(cookieParser({ secret: "your-secret-key" }));
// For enabling CORS
server.beforeEach(cors({
origin: "http://localhost:3000",
credentials: true,
methods: "GET,POST,PUT,DELETE"
}));
// Adding custom middleware functions
server.beforeEach((req, res, next) => {
req.custom = "This is some string";
next();
});
// A middleware function that can be specified to run before some particular routes
const testRouteMiddleware = (req, res, next) => {
req.whatever = "some calculated value maybe";
if (req.params.test !== "something special") {
throw { status: 400, message: "an error message" };
}
next();
};
// Adding route handlers
server.route("get", "/", (req, res, next) => {
return res.render(
"<path-to-file-relative-to-cwd>",
{
test: "some testing value",
number: "2343242"
},
"<mime-type>"
);
});
server.route("get", "/old-url", testRouteMiddleware, (req, res, next) => {
return res.redirect("/new-url");
});
server.route("get", "/api/document/:title", testRouteMiddleware, (req, res) => {
// Reading URL variables (route parameters)
const title = req.params.title;
// Reading URL parameters (query strings) (like /users?filter=active)
const filter = req.query.filter;
// Reading JSON request body
const anything = req.body.anything;
// Handling errors
if (anything === "not-expected-thing")
throw { status: 400, message: "Invalid property." };
// Sending a JSON response
return res.status(200).json({ message: "This is a test response" });
});
// Reading and setting cookies
server.route("post", "/login", (req, res) => {
// Reads are available via req.cookies and req.signedCookies
const sessionId = req.signedCookies.sessionId;
// Set a signed session cookie
res.cookie("sessionId", "abc123", { signed: true, httpOnly: true, secure: true });
return res.status(200).json({ message: "Logged in" });
});
// Sending a file response
server.route("get", "/file", (req, res) => {
// Make sure to specify a correct path and MIME type...
return res.status(200).sendFile("<path-to-file-relative-to-cwd>", "<mime-type>");
});
// Handle all the errors that could happen in the routes
server.handleErr((error, req, res) => {
if (error && error.status) {
return res.status(error.status).json({ error: error.message });
} else {
console.error(error);
return res.status(500).json({
error: "Sorry, something unexpected happened from our side."
});
}
});
server.listen(3000, () => {
console.log("Server has started on port 3000");
});Version 1.x.x represents the initial release of our framework, developed during the Understanding Node.js Core Concepts course. These versions laid the foundation for our project.
All version 2.x.x releases are considered to be in active development, following the completion of the course. These versions include ongoing feature additions and API changes as we refine the framework. Frequent updates may require code changes, so version 2.x.x is not recommended for production environments.
For new features, bug fixes, and other changes that don't break existing code, the patch version will be increased. For changes that break existing code, the minor version will be increased.
Version 3.x.x and beyond will be our first production-ready releases. These versions are intended for stable, long-term use, with a focus on backward compatibility and minimal breaking changes.