30.7 Generating OpenAPI Specs from TypeScript Route Definitions

Right, so you’ve built a beautiful, type-safe API with TypeScript, Express, and Fastify. It feels solid. You’re not sending strings that should be integers or booleans that should be dates. But now someone—a frontend team, a client, your future self who forgot everything—asks for the API documentation. You’re not going to hand-write an OpenAPI spec, are you? That would be like building a perfect, self-documenting castle and then painting the blueprints by hand on the outside wall. We automate this.

30.6 Validation Libraries: Zod and TypeBox for Request Validation

Right, let’s talk about keeping the barbarians at the gate. You’ve got an Express or Fastify endpoint, and you’re trusting the internet—a collection of cats, bots, and malicious actors—to send you well-formed, perfectly typed data. Hilarious, right? We both know any is a lie we tell ourselves to feel safe at night. The request body is a terrifying unknown, and it’s our job to turn it into something we can actually use without blowing up the entire application. That’s where runtime validation comes in, and Zod and TypeBox are your two best friends for this particular trench warfare.

30.5 Fastify Plugins and the Typed Plugin Pattern

Alright, let’s talk about Fastify plugins. This is where Fastify truly flexes its architectural muscles and leaves other frameworks looking a bit… basic. The plugin system isn’t just a way to add features; it’s the fundamental, first-class way you build anything in Fastify. It’s how you encapsulate functionality, manage dependencies, and—crucially for us—how we get TypeScript to understand our entire application’s structure. Think of a Fastify plugin as a neatly wrapped package containing routes, decorators, and hooks. The magic is that these packages can be developed, tested, and reasoned about in isolation before being seamlessly integrated into your main application. It’s the opposite of the “just attach stuff to the app object and pray” pattern you might be used to.

30.4 Fastify with TypeScript: Schema-Based Request Typing

Alright, let’s talk about the part of Fastify that makes TypeScript developers do a little happy dance: schema-based request typing. This is where Fastify flexes hard on other frameworks. While Express has you writing any or awkward req as RequestWithBody casts, Fastify bakes type safety directly into your route’s DNA. It’s not a bolted-on afterthought; it’s the foundation. The magic lies in Fastify’s use of JSON Schema. You define the shape and validation rules for your request’s body, querystring, params, and even headers using a schema. Fastify then does two brilliant things for you: it validates incoming requests against that schema (bye-bye, a lot of your manual validation logic!) and, crucially, it generates strong TypeScript types from that schema. You write the rules, and Fastify hands you the types. It’s like getting the blueprint and the finished product at the same time.

30.3 Typed Middleware: RequestHandler and ErrorRequestHandler

Alright, let’s talk middleware. This is where the rubber meets the road in your server setup. Middleware functions are the bouncers, the paperwork processors, and the emergency crews of your application. They have access to the request object (req), the response object (res), and that all-important next function in the application’s request-response cycle. In vanilla JavaScript, you’d just slap a function in there and hope for the best. But we’re better than that. We have types. TypeScript’s power here is that it lets you define exactly what each middleware expects and what it’s allowed to do, turning runtime guesswork into compile-time certainty.

30.2 Extending express.Request with Custom Properties

Right, so you’re building an Express app and you’ve hit the point where you need to store some custom data on the request object. Maybe you’ve just authenticated a user and you want to slap their entire profile object onto req.user. Your first instinct in TypeScript is probably to just reach out and extend the Request interface. It’s a good instinct, but if you do it naively, you’ll be smacking headfirst into a wall of red squiggles. Let’s break down why that happens and how to do it properly.

30.1 Express with TypeScript: Typed Request and Response

Right, let’s get you set up with Express and TypeScript without losing your mind. The first thing you’ll realize is that Express, bless its heart, was not built with type safety in mind. Out of the box, its Request and Response objects are about as typed as a bowl of soup. Your job is to add the cutlery. The Vanilla (and Painfully Untyped) Problem If you just start writing Express handlers with TypeScript, you’re in for a world of any. Look at this sad, lonely handler:

26.8 TLS Configuration and Let's Encrypt with golang.org/x/crypto/acme

Right, so you’ve built your server, and it’s happily chatting away on port 80. That’s great, if you’re living in 1995. For the rest of us, we need to wrap this whole conversation in the secure, encrypted envelope of TLS. And because you’re not a multi-billion dollar corporation with a dedicated PKI team, you’re going to use Let’s Encrypt. It’s the only sane choice. It’s free, it’s automated, and it just works. The Go team, in their infinite wisdom, didn’t put the full ACME client (the protocol Let’s Encrypt uses) in the standard library, but they did bless an official one: golang.org/x/crypto/acme/autocert. This package is so good it feels like magic, and I’m inherently suspicious of magic. Let’s demystify it.

26.7 Graceful Shutdown with context and server.Shutdown()

Right, so you’ve got your server running. It’s handling requests, serving cat pictures, whatever. Now imagine you need to stop it. You hit Ctrl+C. What happens? If you’re not careful, it drops everything and vanishes like a thief in the night. Active connections are severed mid-download, database writes are abandoned, and you’re left with a corrupted state and a bunch of very confused users. Not cool. We do things properly here. We do graceful shutdown. This means we tell the server, “Hey, finish up what you’re doing, but no new stuff, and then we can go.” The net/http package gives us the tools for this, but you have to wire it up yourself. It’s not magic, it’s just good manners.

26.6 Server Timeouts: ReadTimeout, WriteTimeout, IdleTimeout

Right, let’s talk about timeouts. This isn’t just some box-ticking exercise for your app’s YAML config; this is your first and last line of defense against the chaotic, resource-hungry abyss of the public internet. A server without timeouts is like a hotel with no checkout time—eventually, you’re going to run out of rooms because a bunch of guests decided to live in the lobby, doing nothing. Let’s not run that hotel.

26.5 Serving Static Files with http.FileServer

Right, so you want to serve some static files—CSS, JavaScript, images, that sort of thing. Your first instinct might be to write a handler that opens a file and streams it out. Please, for the love of all that is holy, don’t do that. You’ll get the path wrong, forget to set the Content-Type header, and introduce a hilarious directory traversal vulnerability before lunch. Instead, you’re going to use http.FileServer. It’s a workhorse, it’s battle-tested, and it does almost everything right. I say almost because, well, we’ll get to its quirks.

26.4 Writing Middleware: Wrapping Handlers

Right, so you’ve got a handler. It does a thing. It’s a beautiful, pure function that takes a ResponseWriter and a *Request and just… handles. But now you want it to also log every request. And maybe check for an authentication header. And compress the response. And add security headers. Your first, most horrifying instinct might be to just go into your perfect little handler function and start adding a bunch of log.Println() statements and if blocks. Don’t. You’ll turn it into a tangled mess of orthogonal concerns, and I will personally come to your house and refactor your code while muttering angrily under my breath.

26.3 Enhanced Routing in Go 1.22: Method and Wildcard Patterns

Right, so you’ve graduated from the basic http.HandleFunc and http.Handle tutorials. You’ve built a few routes. And you’ve probably already run into the first major headache of the old ServeMux: its routing is… let’s be charitable and call it “simplistic.” It does prefix matching, which means a route registered at /api/ will happily try to handle a request for /api/things/i/do/not/have. That’s not just annoying; it’s a potential security and logic nightmare. You end up writing a bunch of boilerplate code inside your handler to parse out IDs and validate paths. It feels like you’re fighting the standard library.

26.2 http.ServeMux: Pattern Matching and Route Registration

Right, so you want to build a web server in Go. You’ve probably already found http.ServeMux. It’s the router that ships with the standard library, and it’s your first, and often your best, choice. It’s not the flashiest kid on the block, but it’s reliable, predictable, and doesn’t require a 50-page manual to understand. Think of it as the sturdy, well-worn toolbox in your garage, not the multi-function gizmo from a late-night infomercial that promises to julienne fries.

26.1 http.Handler and http.HandlerFunc

Alright, let’s get our hands dirty with the real meat and potatoes of the Go HTTP server: http.Handler and http.HandlerFunc. This isn’t some abstract, ivory-tower concept; it’s the fundamental contract, the interface that everything else is built upon. If you understand this, you understand how the entire net/http package holds together. The core of it all is the http.Handler interface. I love its simplicity. It’s so small, you might trip over it.

— joke —

...