How to write exceptional documentation
Writing high-quality developer documentation is a challenging task. One reader visiting your site may have a tricky bug to solve, another may be looking for guidance, while a third just wants to get started. Plus, everyone has their own unique blend of experience and learning preferences—how do you cover all this in your docs?
This is my personal approach to crafting holistic, comprehensive documentation that prioritizes developer experience and meets the needs of all.
Sections
Documentation often starts with just an API reference, the bare minimum necessary for developers to use your tool. While this reference is helpful for answering "Which API do I need?", your docs should also include a number of other sections, each useful for resolving more queries, for example:
- How do I set it up? → Quickstart
- How do I use it effectively? → Tutorials
- How do I solve a specific problem? → How-to guides
- How does it work? → Explanations
- What can I do with it? → Examples
- How do I use it in production? → Templates
Together with an API reference, these sections can form the foundation of your docs. Let's explore each, through the lens of Terastore, a tool I've invented for this article.
Quickstart
It's vital to remove as much friction as possible from onboarding, and a polished quickstart section is ideal for this. This is a place for step-by-step guides that get users started with the most basic version of your product—you should have one for each technology you support.
Install Terastore with Node.js
Get started with a Terastore database in your Node.js project
1
Install Terastore into your project.
npm install terastore
2
Set up a Terastore
client on your server, inserting your secret key.
import { Terastore } from "terastore";
const client = new Terastore({
key: "sk_4eC39HqLyjWDarjtT1zdp7dc",
});
3
Create a new table in your dashboard’s console by submitting the following command. Make sure to give it a name you recognise.
CREATE TABLE my_table (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
)
Quickstart guides are a crucial part of your documentation, often determining whether a user adopts your product or moves on without even trying it. They should be written primarily for developers that are new to your product.
Recommendations
- Stay minimal: These instructions should demonstrate the quickest way to get started with your app—don't use more than a couple of paragraphs per step.
- No explanations: Don't write complex explanations, instead leave subtle links to relevant parts of your docs, such as API references or concept pages.
- Instruct the user: Notice the tone I'm using, each step is a basic instruction, almost a command, and nothing more.
- Insert API keys: If your product uses API keys, this is a great place to automatically insert them, removing a layer of friction.
- Use visual steps: Having a page with clearly numbered steps can hugely improve clarity. Get your designers involved, it's important this page is easy to follow, and design is part of it.
- Create a CLI installer: A CLI install command can often greatly simplify setting up an application, removing a number of quickstart steps. Consider if this would be helpful for your tool.
- One guide per technology: If your product supports multiple technologies, you should have a separate guide for each. Avoid creating a single guide that mostly works for all technologies, instead have multiple guides that work perfectly.
- Keep testing them: I cannot emphasize this more—test your quickstart guides again and again. They must work for developers first time, as they often don't give you a second chance. Every time you adjust a guide, follow it from end-to-end and check that every step still works.
- In your dashboard: Onboard users more quickly by inserting your quickstart guides into new projects in your dashboard, eliminating unnecessary clicks.
Examples
- Tailwind CSS → Get started: Clear guides with multiple framework options.
- Liveblocks → Get started: A selector that links to different quickstart guides.
- Clerk → Quickstarts: A number of get started guides, with inserted API keys.
- Stripe → Hosted page: An interactive take that's half quickstart, half tutorial.
- Supabase → Use with React: Simple and readable, with a handy project selector.
- React Email → Automatic setup: Very short & simple due to its CLI installer.
Tutorials
Whilst a quickstart guide is a set of instructions, a tutorial is a learning experience, and a way to teach users how to use different aspects of your product. Tutorials can differ in format, for example they may take the form of a long guide or an interactive code editor, but the purpose is the same—give your users the necessary knowledge to use your product to its fullest.
In some cases, using upsert
will be more helpful than insert
. This is because it also updates any existing items, as well as inserting new ones.
Let's go back to our movie application and update a user's age with upsert
.
db.upsert("Adam", {
age: 37,
});
Because the user already exists in our database, we can see that upsert
has simply updated the age instead of inserting a new user.
It's still best to use insert
when explicitly creating a new row, as it'll throw an error if the key already exists, but in other cases upsert
is generally more useful.
db.upsert
replace existing items?Tutorials should be aimed at users that are completely new to your product, and interested in learning how to use it more deeply.
Recommendations
- Build something tangible: Tutorials tend to work best when directing users how to create something tangible like a mini-app, rather than as just abstract lessons. Doing so will help equip users with methods and thinking patterns they can transfer into their own apps.
- Build it together: Notice the language I'm using, we're building this together, we can see how it works. This is a project we're building together, with the writer's experienced guidance.
- Mention best practices: Make sure to leave short comments about best practices, as this isn't just a place for the reader to learn the APIs, but a place to learn how to use them correctly. Link to other pages for longer explanations.
- Plan carefully: A well planned tutorial can cover multiple aspects of your product in a short amount of time. Think carefully about every topic you need to cover, and plan a natural way to transition through each aspect before you start writing.
- Build in complexity: Start with the simplest aspects and progressively increase in complexity through the tutorial, building on the knowledge the user has already learnt.
- Interactive learning: Add fun elements that'll keep users engaged and aid with learning, such as mini-quizzes or live code demos. Your entire tutorial could even be based inside an in-browser code editor.
- Show, don't tell: Don't tell users what something does, show them with a visuals or live code instead. This is a great way to improve understanding, and simplify your tutorial.
- Sense of achievement: Congratulations! At the end of the tutorial, your users should feel accomplished, and have something to show for it. For example, they've successfully built a demo, which they can then extend further.
Examples
- React → Tic-tac-toe tutorial: An engaging tutorial with interactive elements.
- Svelte → Tutorial: An interactive code editor that teaches with live demos.
- Astro → Build your first blog: A multi-page tutorial that leaves you with an app.
- Liveblocks → Tutorial: A short interactive tutorial that teaches you the basics.
- Next.js → Start building with Next.js: A course teaching each aspect of the tool.
- Lit → Learn: Many interactive tutorials, for both newcomers and advanced users.
How-to guides
How-to guides are action-oriented guides that focus on solving a specific problem or achieving a specific goal. Whether short or long, their purpose is the same—get from A to B as efficiently as possible. This makes how-to guides different to tutorials, which are broader learning experiences.
How to call API endpoints when your database updates
Use webhooks to call your Node.js endpoints after modifications to your database
Using Terastore webhooks it's possible to trigger custom events in your Node.js back end when a database entry is modified. This guide will take you through every step required to set up and trigger events in your Node.js server.
First, create a route handler and verify the webhook event in your endpoint, passing the Node.js request, req
. This will check if the request is valid, and was sent from Terastore's servers.
app.post('/api/data', (req, res) => {
const result = client.verifyWebhookEvent(req);
if (!result.ok) {
return res.status(400).json({ error: "Invalid webhook event" });
}
// Handle your successful event
// ...
});
Next, sign in to your webhook dashboard and create a new webhook for this event.
How-to guides are aimed at helping existing users solve specific problems. Their level of experience with your product will differ depending on the subject at hand.
Recommendations
- Set a clear goal: From the start, be clear about the end goal, and exactly what the user will achieve by following the guide to the end.
- Set requirements: Everyone's app will look different, so it's important that users understand any requirements for the guide, and where the starting point is. Let users know if any knowledge or setup is required, for example above, we state that users need a Terastore client before continuing the guide.
- Use tech support questions: A great place to discover which how-to guides you need is by listening to your technical support channels. Look for questions that have been asked multiple times, or those that you predict will be asked again, and turn the answers into guides.
- Mention workarounds: No product is perfect for everyone, and workarounds will always be needed under some circumstances. A how-to guides section is a great place to detail any hacks, using visible disclaimers.
- Ensure they're discoverable: It's easy to end up with lots of how-to guides, making it difficult for users to find the right one. Make sure your guides are discoverable in a search bar, and consider showing an index page with filters.
- Link to a repo: Because how-to guides have a very specific purpose, it's often easy to link to an example repository highlighting the completed code in context.
- Relatively low-effort: How-to guides don't need to be particularly well planned like tutorials. There's a very specific goal at mind, and sometimes just a couple of paragraphs and code snippets is all you need. Make sure your code works though.
Examples
- Stripe → Create a payment link: Notice that requirements are clearly stated.
- Vercel → Vercel guides: An index of simple how-to guides on various topics.
- Astro → Build a custom image…: Short and useful, with easy to follow steps.
- Sanity → Guides: An index of guides, first-party and written by the community.
- Clerk → Add onboarding flow: Guide with a complementary example repo.
- Liveblocks → How to synchronize your…: A longer guide on a specific topic.
Explanations
Part of your docs should be dedicated to explaining various topics, such as why developers should use your product, how it works, and any concepts that need to be understood. You should also consider outlining your product in an easy to digest way, linking to other parts of your docs. Depending on the product, this could be one page, or many.
Terastore is a high-performance, distributed database designed to handle massive amounts of structured data. Its architecture prioritizes scalability, resilience, and low-latency access, making it suitable for modern data-intensive applications such as analytics, IoT, and real-time data processing.
Terastore's global edge network allows you to keep your data close to your users, reducing latency and improving end-user performance, whilst automatically scaling to your needs.
It's also possible to restrict data to specific regions, ensuring compliance with complex regulatory and data residency requirements.
Reliability is at the core of Terastore's architecture, ensuring data integrity through multi-layered redundancy, including data replication across multiple nodes and geographic regions. Automated failover mechanisms detect and recover from node or network failures, maintaining continuous operations without human intervention.
Explanation pages have different audiences, for example overviews are aimed at new users, whereas deeper explanations are for those at a more advanced level.
Recommendations
- Explain concepts: Describe any general concepts that are needed to understand your product, for example Terabase may talk about schemas and tables.
- Describe features & benefits: List the main benefits of your technology, and link to longer pages where more can be learnt.
- Present use cases: Some products may benefit from a page detailing various use cases, linking to relevant examples that show how to implement each.
- Use illustrations: It's often much easier to explain concepts with images.
- Basic code snippets: If a code-related concept is being explained, showing basic code snippets can be helpful. Link to your API reference for further information.
- Be matter of fact: This is not a place for exaggerated marketing text, have plain and uncomplicated explanations that help developers understand your product, even if there are limitations.
Examples
- Tailwind CSS → Utility-first fundamentals: Underscores the core concepts.
- Next.js → Loading UI & streaming: Conceptual info with diagrams & code.
- React → You might not need an effect: A deep explanation about a specific API.
- Vite → Why Vite: Details on why the tool was built, and the problems it solves.
- Liveblocks → Tiptap: An outline of everything the product enables, with visuals.
- WorkOS → Introduction & concepts: An overview of important information.
API Reference
When we think of documentation, API reference pages are what first come to mind. These pages should contain exhaustive technical information on your APIs, written in a factual style. Every documentation site for developers should have an API reference.
care should be taken when using the force
option, and it's generally not recommended for production usage.
db.dropTable
Drops an existing table. This method allows you to programmatically remove a table from the database. By default, if the table doesn't exist, or if it has dependent views, an error will be thrown.
db.dropTable({ name: "my-table-name" });
name
stringrequiredcascade
boolean = falseoptionalonError
(err: Error) => voidoptionalWhen dropping a table that has a dependent view, an error is typically thrown. However using the cascade
option ensures
that all dependent views relying on the dropped table, are also automatically deleted.
db.dropTable({
name: "my-table-name",
cascade: true, // Drop dependent views
});
This option is most useful when modifying schemas, cleaning up test environments, and removing deprecated features.
Timeouts are important when running long queries with large amounts of data. Timeouts are important when running long queries with large amounts of data.
API references are targeted towards returning developers that have already set up your product, or those looking to dive into the details.
Recommendations
- Start basic and build up: In each section, lead with basic code samples, showing only the simplest use, ignoring optional features. Detail other options in new sections and snippets.
- Predict common uses: Consider the end-product and note the most likely uses for a new API. Show developers how to implement each in clearly titled sections after the initial code snippet.
- Have strong defaults: Don't tell users there's two methods to accomplish a task, and let them choose. You know better than them; pick a default method and lead with it. If necessary, you can mention the second option as a footnote.
- Be the most experienced user: To write great docs, you need to understand your product inside-out. Only an experienced user understands the whole picture, sees the best way to piece each API together, and offers the most helpful advice.
- Build demos before writing: Don't write API docs for new features without trying the actual APIs first. Set up a small demo, play around with it, and you'll almost immediately discover something you otherwise would have missed.
- Anticipate problems: Because you're experienced, and you've tested the APIs, you can see where people might stumble. Anticipate their thought processes and include helpful messages to prevent your users getting stuck.
- Outline error handling & limits: Don't just describe the happy path, explain how to handle each type of error that may occur, and any limits in your service. This way, we can help users avoid a lot of frustration up front.
- People will skim-read: API docs are long, and developers will be skimming through. Use different levels of headings to clearly separate sections into a hierarchy, helping with navigation. Our eyes are drawn towards code too, so add short explanatory comments in code snippets.
- Props, arguments, returns: Detail each of these in tables, with info on their types, whether they're optional, and how they can be used. If an argument has a common use, or is too difficult to explain in a few lines, create a new section elsewhere and link to it.
- Links everywhere: It's important to add links to all parts of your docs, but especially so here. If you mention a function or feature, make its name clickable. API references are long, and it isn't always easy to find the right section.
- Familiar REST API pattern: If your product has a REST API, use the familiar OpenAPI design, like the Resend example below. This way users will immediately recognise what they're looking at.
Examples
- Next.js → <Image>: Highly detailed, lots of practical information and demo links.
- Headless UI → Dropdown menu: Exhaustive examples and component API.
- MDN → Array.prototype.map(): Filled with API details and includes live code.
- Tailwind CSS → Border radius: Common uses clearly illustrated, easy to read.
- Resend → Send email: Many language snippets in the familiar REST API format.
- Liveblocks → @liveblocks/client: Large reference page that goes into great detail.
- Supabase → Insert data: Similar style to the REST API format, but for an SDK.
- Stripe → Error handling: Details on how to handle each API error that can occur.
- Radix UI → Menu Bar: Demos, detailed props info, sandbox links, versioned docs.
Examples
Examples are small demos that illustrate how to implement individual features. Including examples in your documentation, along with their code, is an effective way to highlight capabilities, and guide users towards recommended methods and recipes.
Modify a real-time list
Insert items into a real-time list with useMutation
import { useMutation, useQuery } from "terastore/react";
function Example() {
const names = useQuery((db) => db.select("names"));
const addName = useMutation((db), newName) => {
db.select("names").insert(newName);
}, []);
return (
<>
<div>Names: {names.join()}</div>
<input type="text" onChange={(e) => setNewName()} />
<button onClick={addName}>Add name</button>
</>
);
}
Examples are mostly used for showing new users what can be built with your product, and how it can be built. They're also helpful for returning users looking for a specific feature.
Recommendations
- One feature each: Each example should lay out how to implement a single feature. Apart from that, keep things simple and understandable.
- Display code snippets: Show exactly how each example works by sharing code snippets. If an example uses multiple files, consider adding file tabs to your code box, or creating a file viewer.
- Copy-and-pasteable: Make sure snippets can be easily copied and pasted.
- Sometimes repos are better: Basic examples often work as just code snippets in your docs, but sometimes it makes more sense if your example is a mini-project in a repo, for example if a number of files are used or server-side code is needed.
- Easy set-up: If your example is in a repository, make sure it's easy to set up. Ideally, don't require more than one API key to run the project.
- Interactive demos: Showing live demos of each example in your docs is the
best way to describe the purpose of an example and engage users. If it's
difficult to do this (e.g. your product requires authentication) consider
deploying each example with random demo users and placing them inside
iframes
. - Best for front-end: Examples sections work much better for products with a UI or client-side library. If your product is server-side, these sections may not work too well—consider inserting example snippets into the API reference instead.
- Cheatsheets: For less visual apps, cheatsheets are another way to share different examples. For instance, a database may have a cheatsheet page listing a number of queries, each with a short explanation.
Examples
- tldraw → Read-only: Large selection of examples, each demoing a single feature.
- Liveblocks → Code editor: Interactive source-code viewer and multiplayer demo.
- Vue.js → Markdown editor: A practical demo, editable in an interactive code box.
- React Flow → Examples: Many examples, detailing every common usage pattern.
- Nuxt → Hello world: Full-stack demos in embedded editable sandboxes.
- Sanity → Query cheatsheet: One-line examples with explanatory comments.
Templates
Templates are more complex than examples—they're fully-functional projects that users can extend and learn from. Many problems won't surface until building multi-faceted templates, making them an excellent way to bridge the gap between basic examples and real-world apps.
Commerce starter kit
A fully-featured e-commerce starter kit based on Terastore
Shopping Cart
The Commerce starter kit is a high-performance shopping application powered by Terastore, featuring authentication, product listings, and order processing.
npx create-terastore-app --commerce
Templates are helpful both as a starting point for new apps, and for advanced users looking for implementation recommendations.
Recommendations
- Look for improvements: When building templates, note down everything causing friction when building. Use your notes to improve reference info, create new examples, and consider better APIs.
- Reference material: Consider templates as reference material for users building their own apps. Add lots of code comments throughout, explaining what you're doing and why certain methods have been chosen.
- Starting point: A well-built template could make an excellent starting point for a user's new app. Try to structure it in a way that is easily extendable.
- Use industry best practices: Templates should use industry best practices—this is your chance to show users how to build a high-quality app with your product.
- Deploy live previews: Share live previews of your templates to give users a feel for what your products enable.
- Easy setup: Getting a template running may require accounts and environment variables for various platforms. Make it as easy as possible to set up your templates, using integrations, deploy buttons, CLI installers, and how-to guides.
- Marketing content: Templates make great marketing content; they can showcase fun or impressive features on social media. When you release new features, demo it in your templates, and share it.
Examples
- Next.js → Commerce: A complete customisable store, plus many other templates.
- Next Forge → Home: SaaS template with auth, database, payments, emails, more.
- Sanity → Personal website: Full-fledged template with a number of features.
- Astro → Themes: An index of different templates for blogs, docs, portfolios, more.
- React Email → Templates: A number of complete email templates, ready to use.
- Liveblocks → Starter kit: Fully-featured app showcasing each part of the product.
Summary
These sections can form a firm groundwork for your documentation, ensuring it meets the needs of different developers. However, remember to adapt these guidelines to your tool. Not every product will benefit from each of these sections, for example a simple REST API likely won't need a huge examples page; use your common sense.
My final recommendation is to listen and iterate. No documentation will be perfect first time round, and it's imperative you listen to user feedback, and apply improvements—constantly. Writing great documentation can feel like an uphill battle, but the key is persistence. Keep refining, one revision at a time. Developers will thank you for it.