Skip to content

Build a static site serving GraphQL API reference using Nunjucks templates and magidoc generator.

Notifications You must be signed in to change notification settings

nadvotsky/ftf-graphql-docs

Repository files navigation

ftf-graphql-docs

This repository serves a sample example of bulding a GraphQL API reference using Nunjucks templates and magidoc static documentation website generator.

It contains the necessary tools required to:

  • Merge individual schema components into the single GraphQL Schema file using the @graphql-tools/merge.
  • Transform components using the Nunjucks template engine.
  • Generate a static site using Magidoc.

Table Of Contents

Usage

Note that package.json is configured to use corepack.

For that reason, no pnpm installation is required if corepack enable is executed.

Schema

A schema build process can be started using the following:

pnpm run build

This will create or overwrite the existing file dist/schemas/schema.graphqls.

Static Website

To generate a static website, execute the following command:

pnpm run docs

If no errors occurred during the build, publishable webpages will be available at dist/docs.

Screenshot

Developer Manual

Introduction

This project is built upon:

  • Node.js (version 21 or 20 LTS is recommended)
  • TypeScript
  • tsx, an ESBuild tsc replacement
  • pnpm, mainly because magidoc uses it too
  • graphql-js, a reference implementation of GraphQL for JavaScript
  • magidoc, a static GraphQL API documentation generator
  • nunjucks, a templating language for JavaScript
  • markdownlint, a style checker for Markdown/CommonMark files.
  • pluralize and articles
  • biome, a blazingly fast formatter and linter

Linting and Formatting

All-in one linting, formatting, and fixes applying can be performed using:

pnpm run lint

Schema Merging

A process of merging individual schema file into a single holistic GraphQL Schema is executed with the help of @graphql-tools/merge. This tool employs a more comprehensive algorithm than simple file concatenation, enabling as follows:

  • This facilitates the separation of concerns, enhances organization, improves readability, and simplifies the modification and audit processes.
  • Merging fields of types with the same name.
  • A widely adopted and well-maintained solution that offers first-class functionality, proper error handling, and integration with graphql-js.

In this project, the schema merging process runs recursively through the src/graphql directory. A common notation is to create a separate directory for each component in src/graphql/components.

It is worth noting that reusing types such as Query and Mutation is safe. They will be merged into a single type on demand.

For instance, a basic schema definition in src/graphql/components/user.graphql may look like this:

type User {
    name: String!
}

type Query {
    getUser(id: ID!): User
}

type Mutation {
    setUser(id: ID!, name: String!): User
}

Nunjucks Template Engine

The Nunjucks is a versatile and powerful templating language for JavaScript.

Its utilization offers several advantages, including:

  • Reusable blocks of text: May be very suitable for recurring content such as error messages.
  • A single point of modification: For instance, transitioning from CommonMark headers to lists can be achieved through a single macro, rather than having to manually modify multiple documents.
  • Variable substitution: Simplifies the alteration of recently introduced terms that may change in the future.
  • Extensive library of built-in filters: Provides flexibility and can be expanded to suit specific requirements.
  • Support for custom logic: Enables tasks such as pretty printing or automated linting and fixing.
  • Programming capabilities: Offers a powerful programming environment within the documentation!
  • Expressive Syntax: Its intuitive syntax allows for clear and concise template creation.

Important note that nunjucks is heavily inspired by jinja2 and basically is a port of it. Most of the support cases, documentation, and IDE plugins can be used interchangeably.

Hence, please refer to the following for a more comprehensive guide:

Basics

The template engine operates on the same files as the Schema Merging process. However, it is important to note that this operation is better described as postprocessing, as it does not involve reading files directly but rather utilizes the results of schema merging.

Specifically, the template engine is applied to the merged GraphQL schema Abstract Syntax Tree (AST), where it processes only the descriptions of each field. Consequently, each field is processed independently, with no access to other fields during this operation.

By default, the TemplateEngine class configures Nunjucks to trim spaces of the blocks and throw an error when outputting a null/undefined value.

Notes

Please note that using a trailing comma within templates may result in a syntax error! This applies to object initializers, array initializations, parameter definitions, function calls, and more. Identifying the source of such a problem may be challenging.

❌ This will throw an error:

{% set obj = [
  "Hello",
  "World",
]
%}

✅ This will work as expected:

{% set obj = [
  "Hello",
  "World"
]
%}

Variables

A local variable can be declared using the set tag:

{% set self = "Foo Bar Baz!" %}

Note that there is no restriction on the type of value:

{% set obj = {
  key: true,
  val: 1
}
%}

To display a variable, the following snippet can be used:

{{ self }}

Global variables, in contrast to local ones, are accessible throughout the templates, including macros or nested views.

These variables are populated using the Globals class, which injects all JSON files found in the src/variables into the global namespace.

This class also makes available the following functions:

  • rng: Return a random integer between two values, inclusive.

It is worth mentioning that, similar to JavaScript, both dot and property accessors can be used interchangeably with the same result:

{{ names.occasion }}
{{ names["occasion"] }}

Filters

Filters are special functions that are supposed to transform variables in a straightforward way. They can be called using the pipe operator (|) and chained multiple times.

{{ names.occasion | plural }}
{{ names.occasion | truncate(5, true, "...") }}

With the addition to the rich collection of built-in Nunjucks filters, the Filters class also adds the following filters:

  • article: Add the indefinite article the input.
  • plural: Returns the plural form of the input.
  • singular: Returns the singular form of the input.
  • bold: Apply the bold formatting to the input.
  • italics: Apply the italic formatting to the input.

Macro

A macro tag enables the definition of special parameterized reusable blocks. In most scripting or programming languages, a macro can be interpreted as a function.

{% macro warning(text) %}
> **WARNING**: {{text}}
{% endmacro %}

Macro declaration expressions also support specifying keyword and default arguments.

{% macro warning(text, prefix="**WARNING**: ") %}
> {{ prefix }}{{ text }}
{% endmacro %}

To render a macro, use the macro's name followed by two parentheses (). Arguments can be passed within the parentheses. Named arguments must be prefixed with the parameter name followed by an equal sign (=).

{{ warning("Hello, world!", "[***W***]: ") }}

[W]: Hello, world!

One of the powerful features of macros is their ability to be declared out of the current scope in a separate file and then be imported as needed. There are two types of imports: generic and specific.

Generic Import:

{% import "warning.njs" as warning %}
{{ warning.warning("Hello, world!") }}

Specific Import:

{% from "warning.html" import warning as warn %}
{{ warn("Hello, world!") }}

In this project, the lookup path for the import is set to the src/views directory. The conventional recommendation is to declare views only in this directory, but nested directories are allowed. It is also recommended to declare only one macro per file.

Condition

An if tag is a conditional statement. It is used to execute a block of code only when a specific condition is met.

Unauthorized Error:

{% if reasons | length > 0 %}
  - **Reasons**: {{ reasons | join(", ") }}
{% elif errors | length > 0 %}
  - **Errors**: {{ errors | join(", ") }}
{% else %}
  - **Notes**: Nothing.
{% endif %}

Unauthorized Error:

  • Notes: Nothing.

Loops

A for tag loop is used to iterate over arrays, dictionaries, and anything implementing the iterable protocol.

Iterating over an array:

{% set constraints = ["up to 50 symbols", "A-Z, a-z, and 1-9"] %}
This field has the following constraints:

{% for item in constraints %}
- {{ item | title }}.
{% endfor %}

This field has the following constraints:

  • Up to 50.
  • A-Z, a-z, and 1-9.

Iterating over an object (dictionary):

{% set constraints = {
  length: "up to 50",
  symbols: "A-Z, a-z, and 1-9"
] %}
This field has the following constraints:

{% for key, val in constraints %}
- **{{ key | title}}**: {{ val | title }}.
{% endfor %}

This field has the following constraints:

  • Length: Up to 50.
  • Symbols: A-Z, a-z, and 1-9.

Consideration of Alternatives

Please be advised that Magidoc is not the only tool available for building static GraphQL API documentation. There are several factors that may lead to hesitations regarding the use of Magidoc:

  • Limited Template Options: Currently, only the carbon-multi-page template is available, which can be relatively heavy.
  • Build Time: The build process, utilizing Svelte and Vite, can take up to one minute for a simple schema, posing challenges for CI/CD pipelines and overall experience.
  • Complexity of Building Process: The entire build process involves downloading and running a nested package manager, which may be considered overcomplicated.
  • Limited Runtime Support: Magidoc does not support alternative runtimes such as Deno and Bun.

Given that Magidoc serves as a cornerstone, and the overall codebase side of this repository, exploring alternatives is advisable. One promising approach is to convert the GraphQL schema file into an array of Markdown files and then generate static documentation using various available tools built with high-performance programming languages and web frameworks:

  • gqldoc: The easiest way to make API documents for GraphQL.
  • MkDocs: Project documentation with Markdown.
  • 11ty: Eleventy, a simpler static site generator.
  • mdBook: Create modern online books from Markdown files.
  • docfx: Build your technical documentation site with docfx.
  • Static: A powerful static website generation toolkit suitable for most use cases.

License

This project is released under the MIT License.

MIT License

Copyright (c) 2024 Alexander Nadvotsky

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

About

Build a static site serving GraphQL API reference using Nunjucks templates and magidoc generator.

Topics

Resources

Stars

Watchers

Forks

Contributors