# Nitro Server Entry

> Use a server entry to create a global middleware that runs for all routes before they are matched.

The server entry is a special handler in Nitro that acts as a global middleware, running for every incoming request before routes are matched. It's commonly used for cross-cutting concerns like authentication, logging, request preprocessing, or creating custom routing logic.

## Auto-detected `server.ts`

By default, Nitro automatically looks for a `server.ts` (or `.js`, `.mjs`, `.mts`, `.tsx`, `.jsx`) file in your project root directory.

If found, Nitro will use it as the server entry and run it for all incoming requests.

<code-group>

```ts [server.ts]
export default {
  async fetch(req: Request) {
    const url = new URL(req.url);

    // Handle specific routes
    if (url.pathname === "/health") {
      return new Response("OK", {
        status: 200,
        headers: { "content-type": "text/plain" }
      });
    }

    // Add custom headers to all requests
    // Return nothing to continue to the next handler
  }
}
```

```ts [routes/api/hello.ts]
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  return { hello: "API" };
});
```
</code-group>

<tip>

When `server.ts` is detected, Nitro will log in the terminal: `Detected \`server.ts` as server entry.`
</tip>

With this setup:

- `/health` → Handled by server entry (returns a response)
- `/api/hello` → Handled by the API route handler directly
- `/about`, etc. → Server entry runs first, then continues to the renderer if no response is returned

## Framework compatibility
The server entry is a great way to integrate with other frameworks. Any framework that exposes a standard Web `fetch(request: Request): Response` interface can be used as a server entry.

### Web-compatible frameworks

Frameworks that implement the Web `fetch` API work directly with `server.ts`:

<tabs>

<tabs-item>

```ts [server.ts]
import { H3 } from "h3";

const app = new H3()

app.get("/", () => "⚡️ Hello from H3!");

export default app;
```
</tabs-item>

<tabs-item>

```ts [server.ts]
import { Hono } from "hono";

const app = new Hono();

app.get("/", (c) => c.text("🔥 Hello from Hono!"));

export default app;
```
</tabs-item>

<tabs-item>

```ts [server.ts]
import { Elysia } from "elysia";

const app = new Elysia();

app.get("/", () => "🦊 Hello from Elysia!");

export default app.compile();
```
</tabs-item>
</tabs>

### Node.js frameworks

For Node.js frameworks that use `(req, res)` style handlers (like [Express](https://expressjs.com/) or [Fastify](https://fastify.dev/)), name your server entry file `server.node.ts` instead of `server.ts`. Nitro will automatically detect the `.node.` suffix and convert the Node.js handler to a web-compatible fetch handler using [`srvx`](https://srvx.h3.dev/).

<tabs>

<tabs-item>

```ts [server.node.ts]
import Express from "express";

const app = Express();

app.use("/", (_req, res) => {
  res.send("Hello from Express with Nitro!");
});

export default app;
```
</tabs-item>

<tabs-item>

```ts [server.node.ts]
import Fastify from "fastify";

const app = Fastify();

app.get("/", () => "Hello, Fastify with Nitro!");

await app.ready();

export default app.routing;
```
</tabs-item>
</tabs>

## Configuration

### Custom server entry file

You can specify a custom server entry file using the `serverEntry` option in your Nitro configuration:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  serverEntry: "./nitro.server.ts"
})
```

You can also provide an object with `handler` and `format` options:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  serverEntry: {
    handler: "./server.ts",
    format: "node" // "web" (default) or "node"
  }
})
```

### Handler format

The `format` option controls how Nitro treats the default export of your server entry:

- **`"web"`** (default) — Expects a Web-compatible handler with a `fetch(request: Request): Response` method.
- **`"node"`** — Expects a Node.js-style `(req, res)` handler. Nitro automatically converts it to a web-compatible handler.
When auto-detecting, the format is determined by the filename: `server.node.ts` uses `"node"` format, while `server.ts` uses `"web"` format.

### Disabling server entry

Set `serverEntry` to `false` to disable auto-detection and prevent Nitro from using any server entry:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  serverEntry: false
})
```

## Using event handler

You can also export an event handler using `defineHandler` for better type inference and access to the h3 event object:

```ts [server.ts]
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  // Add custom context
  event.context.requestId = crypto.randomUUID();
  event.context.timestamp = Date.now();

  // Log the request
  console.log(`[${event.context.requestId}] ${event.method} ${event.path}`);

  // Continue to the next handler (don't return anything)
});
```

<important>

If your server entry returns `undefined` or doesn't return anything, the request will continue to be processed by routes and the renderer. If it returns a response, the request lifecycle stops there.
</important>

## Request lifecycle

The server entry is registered as a catch-all (`/**`) route handler. When a specific route (like `/api/hello`) matches a request, that route handler takes priority. For requests that don't match any specific route, the server entry runs before the renderer:

```md
1. Server hook: `request`
2. Route rules (headers, redirects, etc.)
3. Global middleware (middleware/)
4. Route matching:
   a. Specific routes (routes/) ← if matched, handles the request
   b. Server entry ← runs for unmatched routes
   c. Renderer (renderer.ts or index.html)
```

When both a server entry and a renderer exist, they are chained: the server entry runs first, and if it doesn't return a response, the renderer handles the request.

## Development mode

During development, Nitro watches for changes to your server entry file. When the file is created, modified, or deleted, the dev server automatically reloads to pick up the changes.

## Best practices

- Use server entry for cross-cutting concerns that affect **all routes**
- Return `undefined` to continue processing, return a response to terminate
- Keep server entry logic lightweight for better performance
- Use global middleware for modular concerns instead of one large server entry
- Consider using [Nitro plugins](/docs/plugins) for initialization logic
- Avoid heavy computation in server entry (it runs for every request)
- Don't use server entry for route-specific logic (use route handlers instead as they are more performant)
