DEV Community

Cover image for Build a Docusaurus-like Site with FastAPI: Step 3 - Code Highlighting
Leapcell
Leapcell

Posted on

Build a Docusaurus-like Site with FastAPI: Step 3 - Code Highlighting

Cover

In the previous article, we added support for dynamically rendering HTML from .md files.

But as you saw, the Python code block in docs/hello.md was rendered, but it looked plain and had no color highlighting, making it difficult to read.

In this article, we'll solve this problem: using Pygments and python-markdown extensions to add syntax highlighting to our Markdown code blocks.

Step 1: Install Highlighting Dependencies

The python-markdown library supports extra features through "Extensions." To implement code highlighting, we need the codehilite extension, which in turn depends on another library: Pygments.

Execute the following command to install Pygments:

pip install Pygments
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Static Files Directory and CSS

The way syntax highlighting works is a two-step process:

  1. python-markdown's codehilite extension converts code blocks (e.g., python ...) into HTML tags with specific CSS classes (like .k for keyword, .s for string).
  2. Use a CSS file to tell the browser what colors these CSS classes should display.

The Pygments library provides a convenient command-line tool to generate this CSS.

First, let's create a static/css folder in our project's root directory to store this CSS file.

fastapi-docs-site/
├── docs/
│   └── hello.md
├── main.py
├── static/
│   └── css/          <-- New
└── templates/
Enter fullscreen mode Exit fullscreen mode

Step 3: Generate the Highlighting CSS File

Open your terminal and run the following command:

pygmentize -S monokai -f html -a .codehilite > static/css/highlight.css
Enter fullscreen mode Exit fullscreen mode

Here's an explanation of the pygmentize parameters:

  • -S monokai: Specifies monokai as the highlighting theme (you can also try default, github-dark, solarized-light, etc.).
  • -f html: -f indicates that we want to generate CSS for HTML.
  • -a .codehilite: -a means all CSS rules should be nested under the .codehilite CSS class (this is the default class used by python-markdown).

After it finishes, open static/css/highlight.css, and you'll see the CSS rules for the monokai theme have been generated inside.

Step 4: Mount the Static Directory in FastAPI

Now we have the CSS file, but FastAPI doesn't know about it yet. To fix this, we need to "mount" the static directory in main.py.

Open main.py and modify it as follows:

# main.py
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
import markdown
from fastapi.staticfiles import StaticFiles # 1. Import StaticFiles

app = FastAPI()

# 2. Mount the static directory
# This tells FastAPI that any request starting with /static
# should look for files in the "static" folder
app.mount("/static", StaticFiles(directory="static"), name="static")


templates = Jinja2Templates(directory="templates")


# --- Home route (unchanged) ---
@app.get("/", response_class=HTMLResponse)
async def root(request: Request):
    context = {
        "request": request,
        "page_title": "Hello, Jinja2!"
    }
    return templates.TemplateResponse("index.html", context)


# --- Doc route (to be modified next) ---
@app.get("/docs/hello", response_class=HTMLResponse)
async def get_hello_doc(request: Request):
    md_file_path = "docs/hello.md"
    try:
        with open(md_file_path, "r", encoding="utf-8") as f:
            md_content = f.read()
    except FileNotFoundError:
        return HTMLResponse(content="<h1>404 - Document Not Found</h1>", status_code=404)

    # Unchanged for now, we will modify it in the next step
    html_content = markdown.markdown(md_content)

    context = {
        "request": request,
        "page_title": "Hello, Markdown!",
        "content": html_content
    }
    return templates.TemplateResponse("doc.html", context)
Enter fullscreen mode Exit fullscreen mode

Step 5: Include the CSS in the HTML Template

Modify templates/doc.html, adding a <link> tag in the <head> so the HTML page can load the code highlighting CSS:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{{ page_title }} - My Docs Site</title>

    <link
      rel="stylesheet"
      href="{{ url_for('static', path='css/highlight.css') }}"
    />
  </head>
  <body>
    <h1>{{ page_title }}</h1>

    <hr />

    <div class="doc-content">{{ content | safe }}</div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Step 6: Enable Markdown Extensions

For the final step, let's return to main.py and tell the python-markdown library to use the codehilite extension during conversion.

We also need the fenced_code extension, which is used to render content within triple backticks as code blocks, a necessary condition for displaying syntax highlighting.

Modify the get_hello_doc route function in main.py:

# main.py

# ... (other imports and app instance remain unchanged) ...

# ... (app.mount and templates instance remain unchanged) ...

# ... (root home route remains unchanged) ...


@app.get("/docs/hello", response_class=HTMLResponse)
async def get_hello_doc(request: Request):
    """
    Read, parse, and render the hello.md document (with syntax highlighting)
    """
    md_file_path = "docs/hello.md"

    try:
        with open(md_file_path, "r", encoding="utf-8") as f:
            md_content = f.read()
    except FileNotFoundError:
        return HTMLResponse(content="<h1>404 - Document Not Found</h1>", status_code=404)

    # Key change: enable markdown extensions
    # 1. fenced_code: Supports `` ` syntax
    # 2. codehilite: Enables syntax highlighting (it will automatically use Pygments)
    extensions = ['fenced_code', 'codehilite']
    html_content = markdown.markdown(md_content, extensions=extensions)

    context = {
        "request": request,
        "page_title": "Hello, Markdown!",
        "content": html_content
    }

    return templates.TemplateResponse("doc.html", context)
Enter fullscreen mode Exit fullscreen mode

Step 7: Run and Test

Run uvicorn main:app --reload to start the server.

Now, visit http://127.0.0.1:8000/docs/hello again.

You will find that the Python code block in hello.md now has monokai theme syntax highlighting. You can also try changing the parameters when generating the CSS to see the effects of other highlighting themes.

ImageP1

Deploying the Project Online

A docs site is meant to be visited by everyone, so just running it locally isn't enough. Next, we can deploy it online.

A simple deployment option is to use Leapcell. It's a web app hosting platform that can host projects in various languages and frameworks, including FastAPI, of course.

Leapcell

Follow the steps below:

1.Register an account on the website.

2.Commit your project to GitHub. You can refer to GitHub's official documentation for the steps. Leapcell will pull the code from your GitHub repository later.

3.Click "Create Service" on the Leapcell page.

LeapcellImageP1

4.After choosing your FastAPI repo, you'll see Leapcell has auto-populated the necessary configurations.

LeapcellImageP2

5.Click "Submit" at the bottom to deploy. The deployment will complete quickly and return you to the deployment homepage. Here we can see that Leapcell has provided a domain. This is the online address of your blog.

LeapcellImageP3

Conclusion and Next Steps

With syntax highlighting, your documentation site looks much more professional.

But there's still one problem: the page title ({{ page_title }}) in the doc.html template is still hard-coded in the route function in main.py ("page_title": "Hello, Markdown!") instead of being retrieved from the Markdown file.

This is very inflexible. The correct approach is for this title to also be read from the hello.md file itself.

In the next article, we will introduce Frontmatter (used to define article metadata like title, date, etc.) in our Markdown files and implement Frontmatter parsing and reading in our project to avoid hard-coding in our code.


Follow us on X: @LeapcellHQ


Read other articles in this series

Related Posts:

Top comments (1)

Collapse
 
aaron_rose_0787cc8b4775a0 profile image
Aaron Rose

Nice one, Leapcell! 💯🔥