The Elements of APIs

Establishing contracts

Devise a development pipeline where code must follow the flow outlined in the guide. With guards in place, it's technically impossible to wing it, and also it's easier to just do it this one way than to try to do it some other way. Your team is going to take shortcuts when they find them, so just start with the shortcuts.

To me, this means starting with an OpenAPI definition, using code generation to implement your endpoints based on the spec, and using strict linters to ensure your code always conforms to the spec.

This also means that you can make consistency an important rule, but break that rule when you need to. If some particular endpoint really needs an extra field or two that break your esablished patterns, you have a safe way to introduce the discrepancy. Define it in your spec, make sure your server satisfies the spec, and move on. This leaves room for bikeshedding, and that's okay. The most effective tools to resist bikeshedding are shared values and mature conversations.

If you're validating input based on what's permissible by your strictly-defined API, you can do less in your route handlers to guard against ingesting bad data. If you're validating output based on that same definition, you can do less still in your route handlers to guard against transmitting bad data. In many cases, your code can be fully covered by writing one functional test for each response code. If your validation works, you're done!

In Node.js land, a really good piece of middleware for facilitating this process is called express-openapi-validator. (I'm not involved with this project, but I've used it in production and I had a great time.) If you're not using Express.js, take a look at what this middleware does and try to find a similar project, or just emulate in your own codebase. The important behaviors are that you can immediately return a 400 error to the client if they've tried sending a request that doesn't match the spec, and you can return a 500 error to the client if your server tries sending a response that doesn't match the spec. Unexpected input never reaches your server, and unexpected output never reaches your client. This makes it really easy to catch bugs that might have been too subtle to detect otherwise.

Even if you're just writing "a few endpoints," this practice will pay off immediately.