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 ...
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
Step 2: Configure Static Files Directory and CSS
The way syntax highlighting works is a two-step process:
python-markdown’scodehiliteextension converts code blocks (e.g.,python ...) into HTML tags with specific CSS classes (like.kfor keyword,.sfor string).- 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/
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
Here’s an explanation of the pygmentize parameters:
-S monokai: Specifiesmonokaias the highlighting theme (you can also trydefault,github-dark,solarized-light, etc.).-f html:-findicates that we want to generate CSS for HTML.-a .codehilite:-ameans all CSS rules should be nested under the.codehiliteCSS class (this is the default class used bypython-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)
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>
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)
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.
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.
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.
4.After choosing your FastAPI repo, you’ll see Leapcell has auto-populated the necessary configurations.
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.
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: