HTTP and SQLite

Start an HTTP server

Create a small HTTP server with Bun.serve and return a Web Standard Response from its fetch handler.

Bun has an HTTP server built in. You don’t install Express or another framework to answer requests. You call Bun.serve() and give it a fetch function.

That function receives a Web Standard Request and returns a Response. If you’ve used fetch() in the browser, you already know both objects. Here the roles are flipped: instead of sending a request you receive one, and instead of reading a response you build one.

Replace index.ts with:

const server = Bun.serve({
  port: Number(Bun.env.PORT ?? 3000),
  fetch() {
    return new Response('Bun Notes is running')
  },
})

console.log(`Listening on ${server.url}`)

Start the server:

bun --watch index.ts

The terminal prints Listening on http://localhost:3000/. Open that address in your browser, or make the request with curl:

curl http://localhost:3000

The response is:

Bun Notes is running

Add -i to curl to see the status line and headers too. You’ll get HTTP/1.1 200 OK and a Content-Type: text/plain;charset=utf-8 header, both chosen by Response because we passed it a string.

When the port is busy

If another program already uses port 3000, Bun fails to start and prints an error saying the port is in use. Since we read the port from the environment, the fix is one variable:

PORT=3001 bun --watch index.ts

This is why I never hard-code the port. Deployment platforms hand you one through a PORT environment variable, and you want the same code to work there and on your laptop.

Read the request

The request carries the method, the URL, the headers, and the body the client sent. Let’s return a small JSON description of each request, so we can see what arrives:

const server = Bun.serve({
  fetch(request) {
    const url = new URL(request.url)

    return Response.json({
      method: request.method,
      path: url.pathname,
    })
  },
})

console.log(`Listening on ${server.url}`)

request.url is a full string like http://localhost:3000/api/notes. Wrapping it in URL gives us the pieces, and pathname is the part we care about for routing.

Now curl http://localhost:3000/api/notes returns:

{"method":"GET","path":"/api/notes"}

Response.json() serializes the object and sets the Content-Type header to application/json for us.

Request, Response, and URL are the same core APIs used by browsers, Deno, Cloudflare Workers, and Node.js. Nothing here is a Bun invention. That makes the HTTP layer familiar, and it makes it easy to test, because a test can call the same handler with a plain Request object.

Lesson completed