Skip to main content
Ant’s HTTP server model is intentionally minimal. You export a default object with a fetch function, and the runtime starts a server automatically — no setup code, no listen() call, no callback registration. Your handler receives a standard Request and returns a standard Response, which means everything you already know about the Web Fetch API applies here.

The basic server pattern

The smallest possible Ant server is three lines of meaningful code:
export default {
  port: 8000,
  fetch(req) {
    const url = new URL(req.url);
    return new Response('Hello from ' + url.pathname);
  }
};
When Ant detects that the default export has a fetch property, it starts an HTTP listener on the configured port. Every incoming request is passed to fetch as a Request object. The Response you return becomes the HTTP response.
If you omit port, the server binds to a random available port.

Reading the request

The req parameter is a standard Request object. Use new URL(req.url) to parse the URL — do not try to split or regex the raw string.
req.method
string
HTTP method: "GET", "POST", "PUT", "DELETE", etc.
req.url
string
The full request URL string (e.g. "http://localhost:8000/users/42").
req.headers
Headers
HTTP request headers as a Headers object. Use .get(name) to read a header.
export default {
  port: 8000,
  fetch(req) {
    const url = new URL(req.url);
    const userAgent = req.headers.get('User-Agent');

    console.log('request:', req.method, url.pathname);

    return new Response(`path=${url.pathname}, ua=${userAgent}`);
  }
};

Reading the body

export default {
  port: 8000,
  async fetch(req) {
    if (req.method === 'POST') {
      const body = await req.json();
      return Response.json({ received: body });
    }
    return new Response('send a POST', { status: 405 });
  }
};

Building responses

Text response

return new Response('Hello, World!');

Status codes and headers

return new Response('not found: ' + url.pathname, { status: 404 });
return new Response(meow, {
  headers: { 'X-Ant': 'meow' }
});

JSON response

Response.json() is a static helper that sets the Content-Type header for you:
return Response.json({ users: [] });

HTML response

const html = `<!doctype html><h1>Hello from Ant ${Ant.version}!</h1>`;

return new Response(html, {
  headers: { 'Content-Type': 'text/html' }
});

Error responses

export default {
  port: 8000,
  fetch(req) {
    const url = new URL(req.url);

    if (url.pathname === '/') return new Response('Welcome!');
    return new Response('not found: ' + url.pathname, { status: 404 });
  }
};

The server parameter

Your fetch function receives a second argument — the server object. Right now it exposes one method: server.stop(), which gracefully shuts down the HTTP listener.
server.stop()
function
Stops the server after the current request completes. No new connections are accepted after this is called.
let count = 0;
const MAX_REQUESTS = 10;

export default {
  fetch(req, server) {
    if (new URL(req.url).pathname.includes('favicon')) {
      return new Response(null, { status: 404 });
    }

    count++;

    if (count === MAX_REQUESTS) {
      console.log('10 requests served, stopping');
      server.stop();
    }

    return Response.json({
      request: count,
      remaining: Math.max(0, MAX_REQUESTS - count)
    });
  }
};
server.stop() is useful for ephemeral servers — test fixtures, one-shot tasks, or CLI tools that start a server to receive a single webhook.

Routing

Ant does not include a built-in router, but any JavaScript routing library works. The example below uses rou3, a lightweight radix-tree router:
import { createRouter, addRoute, findRoute } from '../rou3';

const router = createRouter();

addRoute(router, 'GET', '/', () => {
  return new Response(`Welcome to Ant ${Ant.version}!`);
});

addRoute(router, 'POST', '/users/:id', (_req, params) => {
  return new Response(`User ID: ${params.id}`);
});

addRoute(router, 'GET', '/users/:id/posts', (_req, params) => {
  return new Response(`Posts for user: ${params.id}`);
});

addRoute(router, 'GET', '/api/v1/users', () => {
  return Response.json({ users: [] });
});

export default {
  port: 8000,
  fetch(req) {
    const url = new URL(req.url);
    console.log('request:', req.method, url.pathname);

    const result = findRoute(router, req.method, url.pathname);
    if (result?.data) return result.data(req, result.params);

    return new Response('not found: ' + url.pathname, { status: 404 });
  }
};

Async handlers

Your fetch function can be async. Ant awaits the returned promise before sending the response.
addRoute(router, 'GET', '/status', async () => {
  await new Promise(resolve => setTimeout(resolve, 1000));
  const result = await Promise.resolve('Hello');
  return new Response(`server is responding with ${result}`);
});

Serving files

Use ant:fs and ant:path to read files from disk and serve them:
import { readFile } from 'ant:fs';
import { join } from 'ant:path';

addRoute(router, 'GET', '/fs/meow', async () => {
  const file = await readFile(join(import.meta.dirname, 'meow.txt'));
  return new Response(file || 'none');
});
See the filesystem page for details on ant:fs.