Server Next

tests

A modern JavaScript server with the essentials built in: routing, authentication, uploads, WebSockets and testing.

npm install @server/next
import server from "@server/next";

export default server()
  .get("/", () => "Hello world")
  .post("/users", ctx => {
    console.log(ctx.body);
    return { id: 100 };
  });

Batteries included

Routing, sessions, uploads, validation, websockets, auth and many more.

Authentication

Supports Google, Github, Facebook, email, etc. login with cookies or tokens.

Fully typed

From route params to request bodies so you can write with confidence.

Integrations

The same code runs on Node, Bun, Cloudflare, Netlify and more.

Testing

Testing helpers built for speed, with no mocking or network calls needed.

Documented

Extensive documentation, examples and tutorials for you and your LLM.

Authentication built in

Add OAuth (GitHub, Google, Microsoft, Discord, Facebook, Apple) or email and password with a single option.

Sessions, signed cookies and CSRF-protected flows are handled for you, and the signed-in user is available on ctx.user.

Read more →
import server from "@server/next";
import kv from "polystore";

const auth = "cookie:github"; // sign in /auth/login/github
const store = kv(new Map()); // in-memory sessions

export default server({ store, auth })
  .get("/me", ctx => ctx.user || 401);

File uploads

Files stream straight to storage as they arrive, never held in memory, so even large uploads stay fast and cheap.

Point uploads at a local folder or an S3-compatible bucket, and each stored file shows up on ctx.body.

Read more →
export default server({ uploads: "./uploads" })
  .post("/avatar", ctx => {
    console.log(ctx.body.avatar);
    // {
    //   id: "f3a9c2e1.png",
    //   name: "me.png",
    //   path: "/home/.../uploads/f3a9c2e1.png",
    //   type: "image/png",
    //   size: 345465,
    // }
  });

JSX rendering

On Bun you can return JSX from a route and it is sent as HTML. It is opt-in by updating your tsconfig.json.

This is the primary recommended templating engine, but it is NOT React, just JSX. Interpolation is escaped and safe from XSS.

Read more →
export default server()
  .get("/", () => (
    <html lang="en">
      <body>
        <h1>Hello {name}</h1>
      </body>
    </html>
  ));

HTMX

No integration needed, return HTML fragments from your routes and let HTMX swap them in.

It blends especially well with JSX, whose escaped interpolation keeps those fragments safe from XSS.

Read more →
import server, { file } from "@server/next";

export default server()
  .get("/", () => file("index.html"))
  .post("/clicked", () => (
    <button hx-post="/clicked">Clicked!</button>
  ));

Typed parameters

Route parameters are read straight from the URL and typed for you, so mistakes are caught as you type.

Add a format like :id(number) and the value arrives as a number, and mark optional segments with ?.

Read more →
server()
  // ctx.url.params.name is a string
  .get("/user/:name", ...)
  // ctx.url.params.id is a number
  .get("/user/:id(number)", ...)
  // ctx.url.params.cId is optional
  .get("/books/:id/comments/:cId?", ...);

Upload validation

Add size and type limits right on the uploads option, and oversized or wrong-type files are rejected before your handler runs.

Each stored file gets a random, unguessable id, so nothing is overwritten or predictable.

Read more →
export default server({
  uploads: {
    bucket: "./uploads",
    maxSize: "5mb",
    fileType: ["image/jpeg", "image/png"],
  },
}).post("/avatar", ctx => ctx.body.avatar);

Proxy with fetch()

Return a fetch() response straight from a route and it is forwarded as-is: status, headers and a streamed body.

That is all you need for a backend-for-frontend, an API gateway, or hiding an upstream key from the browser.

Read more →
export default server()
  .get("/gh/*", ctx =>
    fetch(`https://api.github.com${ctx.url.pathname.slice(3)}`)
  );

WebSockets

Handle WebSocket events with the same routing API. Reply to a single client with ctx.socket, or broadcast to every connection with ctx.sockets.

Read more →
server()
  .get("/", () => file("./index.html"))
  .socket("message", ctx => {
    ctx.socket.send(`You said: ${ctx.body}`);
    ctx.sockets.forEach(s => s.send(ctx.body));
  });

Simple responses

Return a string, object, status code, file or stream from any route handler.

Server.js infers the response, content type and headers, so handlers just return data.

Read more →
import server, { file } from "@server/next";

server()
  .get("/text", () => "Hello")
  .get("/json", () => ({ ok: true }))
  .get("/gone", () => 404)
  .get("/file", () => file("./a.pdf"));

Testing built in

Call .test() on any server for an in-process client that runs requests through the full middleware stack, with no ports, mocking or network.

It mirrors every HTTP method and sends JSON, FormData or streams as the body. Each call returns a standard Response, so you assert on the same status, headers and .json() a real client would get.

Read more →
const api = server()
  .get("/hello", () => "Hi!")
  .post("/echo", ctx => ctx.body)
  .test();

const res = await api.post("/echo", { name: "Francisco" });

expect(res.status).toBe(200);
expect(await res.json()).toEqual({ name: "Francisco" });