← Blog

ts-bedrock — the argument, in a repository

We had been building TypeScript projects the same way for years, and every new one started with the same three days: set up the strictness, re-derive the folder layout, re-explain to whoever joined why an Email is not a string. The knowledge lived in our heads and in the last project we happened to remember. That is not a methodology. That is a habit with good intentions.

So we wrote it down as a repository.

Five levels of types

The organising idea is TypeSpec: an application is specified, top to bottom, as five levels of types.

Each level has one home in the tree. Core/App holds T1, Core/Api holds T3, Api/src/Database holds T2, Web/src/State and Web/src/Action hold T4 and T5. When a new engineer asks where something goes, the answer is a lookup, not a debate.

One Core, imported by both sides

Core is a plain TypeScript directory that both the Express API and the Vite frontend import — not a generated client, not a copied interface, not an OpenAPI schema kept in sync by a build step. The same value describes the endpoint on both ends of the wire.

That single decision is where the guarantee comes from. Change a route, a parameter, an error code or a payload field, and every layer that now disagrees stops compiling. There is no drift to detect because there is no second copy to drift.

The API handlers underneath are pure functions from decoded params to Result<ErrorCode, Payload> — no Express in their signature, so testing one requires no mocks and no server.

The rules are machine-checked, not cultural

A style guide that lives in a wiki loses to a deadline. So the constraints are lint errors with --max-warnings=0:

None of these are new ideas. What is new, for us, is that a tired engineer at 5pm cannot quietly opt out of any of them.

What it is for

ts-bedrock is not a framework you extend; it is the starting point we clone. It is deliberately small enough to read in an afternoon and boring enough to survive contact with a real client deadline. Everything we have learned about shipping typed software is either in that repository or is a bug in it.

github.com/haniker-dev/ts-bedrock — open source. The methodology it implements now has a home of its own at typefirst.io.