---
url: /blog/2026/v0.5.2.md
description: >-
  Two additions with little in common, except that both started as annoyances. A
  folder that builds a process instead of serving routes, and middleware that
  runs before validation.
---

Two things in `0.5.2`, and they have little in common,
except that both started as annoyances.

## Sidecars

Every backend eventually grows something that is not a request handler.
A mail sender. A queue consumer. A cron runner.

This was needed in every project, for as long as I can remember. I just never named it.
So everyone did the same: reached for a third-party builder,
and built the extra service outside the framework.

A reasonable move, but a workaround for a missing piece:
a folder that builds something, but does not serve anything.

Then came the big August refactor, and the idea finally crystallized.
Not as a nice-to-have. As a must-have.

So, as of `0.5.2`, [sidecars](/sidecar/intro) are generally available:
a source folder that declares an entry point to build, and nothing else.
No base URL, no routes, no `pages/`, no `api/`.

```sh [create a sidecar, a mailer, say]
pnpm sidecar
```

```txt [added to src/ just like any source folder]
src/
  front/      React + Hono, base "/"
  mailer/     sidecar, no pages/ no api/
    entry.ts
    run.ts
```

Everything else just keeps working, because it is still a source folder.
`~/` and `@/` resolve as usual, typecheck covers it, and `pnpm build mailer` builds it on its own.

The service lives in `entry.ts`: how to start it, and how to stop it.

```ts
// src/mailer/entry.ts
import { defineService } from "_/sidecar";

export default defineService({
  async start() {
    const server = await listen();
    return async () => {
      await server.close();
    };
  },
});
```

The seeded `run.ts` is the process around it, catching signals and shutting down cleanly.
In development `kosmo serve` imports the entry and reloads it on every change.
In production, your process manager runs the runner:

```sh
node dist/mailer/sidecar/run.js
```

Not a new kind of folder. The same folder, finally allowed to not answer requests.

## Edge middleware

The other one took a while to show up.

Validation runs before middleware. Usually that is what you want:
by the time your code runs, the request is known to be well-formed.

For auth it is exactly wrong.
A request with an expired token and a bad body answers `400`,
and auth, the one that would have answered `401`, never gets a turn.

So now a slot whose name starts with `edge:` moves to the front of the route's chain,
before validation. Same `use()`, same handler, nothing new to learn:

```ts
// api/use.ts
export default [
  use(requireSession, {
    slot: "edge:auth", // [!code hl]
  }),
];
```

Now an expired token gets `401`, whatever the body looks like.

It is still a slot, so it can live in `api/use.ts`, in a cascading `use.ts`, or in a route,
and a subtree can replace it by reusing the name.

One thing it is not: app middleware in `api/app.ts` still runs first, for every request,
matched or not. Edge middleware starts at the route, and nothing reaches it
unless a route matched.

More in the [edge middleware](/backend/edge-middleware) docs.
