Selectively Serving Your API Reference
What if you could automate the way you serve your API reference to different types of consumers?
What are you looking for when you make an HTTP GET request to the root path of an API? What if, instead, you open the root URL of an API on your browser? To me, the root path of any API should be a resource holding the full reference of the API. If it's a machine reading it, it should return a machine-readable reference. If it's a human, it should return a nice-looking API reference. Keep reading if you think this is a good idea and you want to know how to put it together.
This article is brought to you with the help of our supporter, Scalar.
Scalar is a suite of powerful & customizable developer tools to help you at all stages of API development. Create beautiful API Documentation in a notion-like editing experience.
The other day, I read something about an API reference that caught my attention. Someone shared that a large company was directly sharing the reference of one of its APIs instead of publishing a full API portal. Why would they do that instead of producing what we believe are the elements of good API documentation? Which, by the way, also includes a full API reference. At first, I didn't think too much about it. But, as time went by, I started seeing it as a great example of the very minimum API documentation you can have.
Having a full reference is critical because it's the comprehensive source of truth of your API. Being able to have one isn't complicated. As long as you have a machine-readable description document, e.g., an OpenAPI document, you can have a full reference for free. Also, a reference doesn't have to limit itself to the technical details of the API operations. It can—and should—include more human-friendly documentation. This can include onboarding information and examples, among other things. Knowing this, I'd choose to start with a reference over building a more sophisticated API portal. An API reference is something I could evolve and add more information to, with little or no upfront investment.
It's clear that having an API reference is something easy. So, how does it work? You can plug your OpenAPI document into any open-source API reference generator. You'll immediately get a good-looking reference Web site you can share with your consumers. You then deploy the generated API reference to a URL of your choice. Any time someone opens that URL, they'll get the full generated reference of your API. This is great if all you want is to serve the API reference in a human-readable way. webIf you also want to serve its machine-readable embodiment, there's something else you need to do.
The machine-readable version of your API reference is its OpenAPI document. Yes, it's as simple as that. The OpenAPI document contains all the information needed to consume your API. Additionally, it can also contain machine-readable examples that consumers can use to generate a mock server. Serving it to consumers is as simple as having the static OpenAPI document on the root path of your API. If consumers make a request to the root path, they'll receive a JSON version of the OpenAPI document, which they can use to "understand" and navigate the API.
What about the generated API reference? It was also being served on the root path of the API. How can we have both the human-friendly reference and the JSON OpenAPI document at the same time? A relatively simple way to serve both types of document selectively is to use content negotiation. If someone requests an HTML document, they'll get the API reference. This will happen if someone opens the root path of the API using a Web browser. If consumers request a JSON document, they'll get the OpenAPI document. In this case, consumers would have to explicitly add the Accept: application/json
header to their request.
Being able to serve two content types on the same path is as simple as using the res.format()
Express method. If you're using another framework, I'm sure there's a way to configure content negotiation. The res.format()
method offers a way to decide which content to respond with based on the request Accept
header, as I explained above. The following example (redacted for brevity) shows how you can respond with the OpenAPI JSON description or the HTML API reference, depending on what the consumer wants.
const fs = require('fs');
const express = require('express');
const router = express.Router();
const createError = require('http-errors');
const YAML = require('yaml');
const { apiReference } = require('@scalar/express-api-reference');
router.get('/', function(req, res, next) {
try {
const openapi = YAML.parse(fs.readFileSync('./openapi.yaml', 'utf8'));
res.format({
json: function() {
res.json(openapi);
},
html: function() {
apiReference({
spec: {
content: openapi
}
})(req, res, next);
}});
} catch (err) {
console.log(err);
next(createError(500, {
details: `Error fetching ${req.originalUrl}`,
instance: req.originalUrl
}));
}
});
module.exports = router;
As you can see, it's not that hard to serve your API reference in multiple formats depending on what consumers are interested in. By doing it, you'll make it easier for consumers—both humans and machines—to understand your API. So, why not do it? If it's so simple, it should be standard procedure, right? That's what I think.