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 };
  });

Fully typed

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

Authentication

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

Batteries included

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

Integrations

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

Testing

Testing helpers built-in, with no ports, mocking or network needed.

Documented

Extensive documentation and examples for every feature.

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.

import server from "@server/next";

const auth = "cookie:github";

// visit /auth/login/github to sign in
export default server({ store, auth })
  .get("/me", ctx => ctx.user || 401);

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 ?.

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?", ...);

File uploads

Uploaded files are streamed to storage as they arrive rather than buffered in memory, so large uploads stay inexpensive.

Point uploads at a local folder or an S3-compatible bucket, and read the stored file from ctx.body.

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,
    // }
  });

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.

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

Runs everywhere

The same file runs on Node, Bun, Cloudflare Workers and Netlify Functions, with no adapters or platform-specific entry points.

import server from "@server/next";

// Same file on Node, Bun, Cloudflare, Netlify
export default server()
  .get("/", () => "Hello from anywhere");

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.

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.

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",
});

Created by Francisco.