Axum Blog - Modern Rust Blog Engine
A high-performance, SEO-optimized blog platform built with Rust, Axum, and Handlebars. Features a clean admin panel, GitHub integration for content sync, and comprehensive security measures.
Table of Contents
- Features
- Tech Stack
- Quick Start
- Configuration
- Usage
- Project Structure
- Creating Blog Posts
- Admin Panel
- GitHub Integration
- API Reference
- Security
- Development
- Deployment
- Troubleshooting
Features
Core Functionality
- Clean, minimal black and white UI with responsive design
- SEO-optimized with meta tags, Open Graph, Twitter Card support
- Markdown-based content with syntax highlighting
- Tag-based post organization and filtering
- Full-text search across posts and content
- RSS feed generation for subscriber distribution
- XML sitemap for search engine indexing
- Reading time estimates on all posts
Admin Panel
- Secure authentication with password protection
- Create, edit, and delete blog posts
- Rate limiting (5 attempts, 5-minute lockout)
- Session-based access control with 1-hour timeout
- Modern, intuitive dashboard interface
- Post management with table-based layout
Content Management
- GitHub repository README sync to blog posts
- Automatic import of README files as blog content
- Link management between posts and GitHub repos
- Repository metadata display (stars, language, last updated)
- One-click sync for linked repositories
SEO & Performance
- Heading anchor IDs for table of contents navigation
- Structured data (JSON-LD) for search engines
- Canonical URL tags
- Optimized images with fallbacks
- Google Fonts integration (Inter)
- Fast server response times with Rust performance
Tech Stack
- Language: Rust
- Web Framework: Axum (async/await, minimal overhead)
- Templating: Handlebars
- Markdown Processing: pulldown-cmark with syntax highlighting
- Syntax Highlighting: Syntect
- Serialization: Serde
- HTTP Client: Reqwest (for GitHub API)
- Async Runtime: Tokio
- Configuration: Dotenvy for environment variables
Quick Start
Prerequisites
- Rust 1.70+ (install from https://rustup.rs/)
- Cargo (comes with Rust)
Installation
- Clone or extract the repository:
cd axum-blog
- Create environment configuration:
cp .env.example .env
- Edit
.envwith your settings:
SITE_URL=http://localhost:8080 ADMIN_PASSWORD=your_secure_password GITHUB_USERNAME=your_username # Optional: add GitHub token for higher API limits GITHUB_TOKEN=your_github_token
- Run the development server:
cargo run
Server will start at: http://localhost:8080
Build for Production
cargo build --release ./target/release/axum-blog
Configuration
Environment Variables
Create a .env file in the project root with the following variables:
SITE_URL (required)
- Base URL for your blog
- Used for canonical links and RSS feed
- Example:
http://localhost:8080orhttps://myblog.com
ADMIN_PASSWORD (required)
- Password for admin panel access
- Should be strong and unique
- Example:
your_secure_password_here
GITHUB_USERNAME (optional)
- Your GitHub username for repository integration
- Used for fetching your public repositories
- Example:
aryansrao
GITHUB_TOKEN (optional)
- GitHub personal access token
- Increases API rate limits from 60 to 5000 requests/hour
- Create at: https://github.com/settings/tokens
- Requires:
repoandpublic_reposcopes
Security Headers
The application automatically sends these security headers:
- X-Frame-Options: DENY (prevents clickjacking)
- X-Content-Type-Options: nosniff (blocks MIME sniffing)
- X-XSS-Protection: 1; mode=block (enables XSS filtering)
- Strict-Transport-Security: max-age=31536000 (HSTS)
- Content-Security-Policy (controls resource loading)
Usage
Accessing the Blog
Public Pages
- Homepage: http://localhost:8080/
- Individual Post: http://localhost:8080/blog/{slug}
- Tag Page: http://localhost:8080/tag/{tag-name}
- RSS Feed: http://localhost:8080/rss.xml
- Sitemap: http://localhost:8080/sitemap.xml
Search
- Index page: Search all posts by title, tags, summary, and content
- Post page: Search within the current post content only
Admin Panel Access
- Navigate to: http://localhost:8080/admin
- Enter your ADMIN_PASSWORD
- Access the dashboard to manage posts
Admin Features
- Create new posts with title, content, tags, and summary
- Edit existing posts while preserving metadata
- Delete posts with confirmation
- View all posts in a table with edit/delete options
- Import GitHub repositories as blog posts
- Sync linked repositories for content updates
Project Structure
axum-blog/ ├── src/ │ └── main.rs # Main application file (~2500 lines) ├── templates/ │ ├── index.html # Homepage template │ └── single.html # Single post template ├── content/ │ └── *.md # Blog post markdown files ├── Cargo.toml # Project dependencies ├── .env.example # Environment configuration template ├── README.md # This file └── .gitignore # Git ignore rules
Content Directory
The content/ directory stores all blog posts as Markdown files. Each file follows this naming convention:
{slug}.md
Examples:
getting-started.mdrust-async-guide.mdgithub-project-name.md(for imported repos)
Creating Blog Posts
Post Format
Blog posts are Markdown files with YAML front matter:
--- title: Your Post Title summary: Brief description for search results and RSS feed author: Your Name tags: rust,web,programming image: https://example.com/image.jpg date: 2024-12-19 --- # Your Post Title Your content here in Markdown format... ## Section Heading More content with **bold**, *italic*, and `code`.
Front Matter Fields
| Field | Required | Description |
|---|---|---|
| title | Yes | Post title, used in page title and meta tags |
| summary | Yes | Short description for search results and feed |
| author | Yes | Author name |
| tags | Yes | Comma-separated tags for categorization |
| image | Yes | Featured image URL for OG tags |
| date | Yes | Publication date in YYYY-MM-DD format |
Markdown Features
- Standard Markdown syntax
- Code blocks with syntax highlighting (specify language: ```rust)
- Heading anchor IDs automatically generated
- HTML tables
- Lists (ordered and unordered)
- Blockquotes
- Links with proper formatting
Example Post
--- title: Getting Started with Rust Web Development summary: Learn how to build web applications with Rust and Axum framework author: John Doe tags: rust,web,axum,tutorial image: https://example.com/rust-web.jpg date: 2024-12-19 --- # Getting Started with Rust Web Development Rust is becoming increasingly popular for web development... ## Why Choose Rust? - Performance comparable to C/C++ - Memory safety without garbage collection - Strong type system - Growing ecosystem ## Your First Axum Application ```rust use axum::{routing::get, Router}; async fn hello_world() -> String { "Hello, World!".to_string() } #[tokio::main] async fn main() { let app = Router::new() .route("/", get(hello_world)); axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); }
Continue your content…
## Admin Panel ### Authentication The admin panel is protected by password authentication: 1. Navigate to /admin 2. Enter the password configured in .env (ADMIN_PASSWORD) 3. Session tokens are valid for 1 hour 4. Rate limiting: 5 failed attempts trigger 5-minute lockout ### Dashboard The dashboard displays: - Quick stats (total posts, total tags, recent updates) - Complete list of posts with metadata - Edit and delete options for each post - Buttons to create new posts or access GitHub integration ### Creating Posts 1. Click "New Post" in dashboard 2. Fill in the form fields: - Title - Content (Markdown) - Tags (comma-separated) - Summary 3. Click "Save Post" 4. Post appears immediately on the blog ### Editing Posts 1. Find the post in the dashboard table 2. Click the edit icon 3. Modify the content 4. Click "Save Post" 5. Changes take effect immediately ### Deleting Posts 1. Find the post in the dashboard table 2. Click the delete icon 3. Confirm the deletion 4. Post is permanently removed ## GitHub Integration ### Linking Repositories The GitHub integration allows you to sync README files from your repositories as blog posts. 1. In admin dashboard, click "GitHub Repos" 2. View your public repositories 3. Click "Import" on a repository 4. A post is created with the repository name and README content 5. Repository metadata is saved for syncing ### Syncing Repositories After linking repositories, you can manually sync to get latest changes: 1. Go to "GitHub Repos" section 2. Click "Sync" next to the linked repository 3. README content is updated on your blog 4. Post slug remains: `github-{repo-name}` ### Automatic Sync Currently, syncing is manual. To set up automatic sync: 1. Configure a GitHub webhook on your repository 2. Point it to your blog's sync endpoint 3. Implement webhook verification (see API Reference) ## API Reference ### Public Endpoints **GET /** - Returns the blog homepage with all posts - Query parameters: - `tag`: Filter posts by tag (e.g., `/?tag=rust`) **GET /blog/{slug}** - Returns a single blog post - Parameters: - `slug`: Post slug (e.g., `getting-started`) **GET /tag/{tag-name}** - Returns posts filtered by tag - Parameters: - `tag-name`: Tag name (URL-encoded) **GET /api/search** - Search across all posts - Query parameters: - `q`: Search query (required, minimum 1 character) - Response: ```json { "results": [ { "title": "Post Title", "slug": "post-slug", "summary": "Post summary", "date": "Dec 19, 2024", "date_iso": "2024-12-19T00:00:00", "tags": ["rust", "web"], "reading_time": 5 } ] }
GET /rss.xml
- RSS feed with all posts
- Compliant with RSS 2.0 specification
- Includes post content, author, and metadata
GET /sitemap.xml
- XML sitemap for search engines
- Includes all posts with last modification date
- Compliant with sitemaps.org protocol
GET /robots.txt
- Search engine crawler instructions
Admin Endpoints
All admin endpoints require valid session authentication.
GET /admin
- Admin login page
POST /admin/login
- Authenticate with password
- Sets secure session cookie
- Body:
password=your_password
GET /admin/dashboard
- Admin dashboard with post management
- Requires authentication
GET /admin/new
- Create new post form
- Requires authentication
GET /admin/edit/{slug}
- Edit post form
- Requires authentication
POST /admin/save
- Create or update post
- Requires authentication
- Body: Form data with title, content, tags, summary, slug (optional)
DELETE /admin/delete/{slug}
- Delete a post
- Requires authentication
GET /admin/github
- GitHub integration page
- Requires authentication
GET /admin/api/repos
- List user’s GitHub repositories
- Requires authentication
- Response: JSON array of repository objects
POST /admin/github/import
- Import GitHub repository as post
- Requires authentication
- Body:
repo_name=repository-name
POST /admin/sync/{slug}
- Sync linked repository content
- Requires authentication
Security
Authentication
- Session-based authentication with secure tokens
- Passwords compared using constant-time comparison
- Session tokens expire after 1 hour of use
- Secure HttpOnly cookies prevent JavaScript access
Rate Limiting
- Login attempts: 5 per IP address
- Lockout duration: 5 minutes after exceeding limit
- Counter resets after lockout period
- Prevents brute force attacks
HTTPS Recommendations
For production deployment:
- Use HTTPS only (enable HSTS via proxy)
- Set secure headers (handled by application)
- Use strong admin password (minimum 12 characters recommended)
- Rotate GitHub tokens regularly
- Monitor admin access logs
- Keep dependencies updated:
cargo update
Data Protection
- No user data collection beyond sessions
- No analytics or tracking
- GitHub tokens stored in environment variables only
- Passwords never logged
Development
Development Server
cargo run
Server runs in debug mode with:
- Faster compilation
- Debug symbols
- No optimizations
Building
# Development build (fast compilation, slow execution) cargo build # Release build (slow compilation, fast execution) cargo build --release
Code Quality
Check for issues:
# Format check cargo fmt --check # Linting cargo clippy # Tests (if implemented) cargo test
Project Dependencies
Key dependencies and their purposes:
axum: Web frameworktokio: Async runtimeserde: Serialization/deserializationhandlebars: Template renderingpulldown-cmark: Markdown to HTML conversionsyntect: Syntax highlightinguuid: Session token generationreqwest: HTTP client for GitHub APIchrono: Date/time handling
Update dependencies:
# Check for updates cargo outdated # Update all cargo update # Update specific package cargo update -p package-name
Deployment
Production Build
cargo build --release
Binary location: ./target/release/axum-blog
Environment Setup
- Copy
.env.exampleto.env - Update production values:text
SITE_URL=https://yourblog.com ADMIN_PASSWORD=strong_unique_password GITHUB_USERNAME=your_username
- Keep
.envsecure and out of version control
Running the Server
# Direct execution ./target/release/axum-blog # With environment loading export $(cat .env | xargs) && ./target/release/axum-blog
Systemd Service
Create /etc/systemd/system/axum-blog.service:
[Unit] Description=Axum Blog Service After=network.target [Service] Type=simple User=www-data WorkingDirectory=/opt/axum-blog EnvironmentFile=/opt/axum-blog/.env ExecStart=/opt/axum-blog/axum-blog Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload sudo systemctl enable axum-blog sudo systemctl start axum-blog sudo systemctl status axum-blog
Reverse Proxy (Nginx)
server { listen 443 ssl http2; server_name yourblog.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; ssl_protocols TLSv1.3 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 80; server_name yourblog.com; return 301 https://$server_name$request_uri; }
Docker Deployment
Create Dockerfile:
FROM rust:latest as builder WORKDIR /app COPY . . RUN cargo build --release FROM debian:bookworm-slim RUN apt-get update && apt-get install -y ca-certificates WORKDIR /app COPY --from=builder /app/target/release/axum-blog . COPY --from=builder /app/templates templates/ COPY --from=builder /app/content content/ COPY .env.example .env EXPOSE 8080 CMD ["./axum-blog"]
Build and run:
docker build -t axum-blog . docker run -p 8080:8080 --env-file .env axum-blog
Troubleshooting
Server won’t start
Error: “Address already in use”
- Another service is running on port 8080
- Solution: Kill the existing process or change port (modify main.rs)
lsof -i :8080 kill -9 <PID>
Admin panel returns 404
Issue: /admin route not found
- Verify server is running:
curl http://localhost:8080/ - Check browser cache or use incognito mode
- Restart server
Posts not appearing
Issue: Blog shows no posts
- Check
content/directory exists and has .md files - Verify markdown files have correct front matter
- Check server logs for parsing errors
- Restart server to reload content
GitHub import fails
Error: “Repository not found” or API errors
- Verify
GITHUB_USERNAMEis correct in .env - Check GitHub repository is public
- Add
GITHUB_TOKENto increase rate limits - Verify internet connection
Search not working
Issue: Search returns no results
- On homepage: searches all posts and content
- On post page: searches only within that post
- Ensure posts have content in body (not just title)
- Check that content is valid markdown
Session expires too quickly
Issue: Getting logged out from admin
- Session timeout is 1 hour by default
- Check system clock is correct
- Clear browser cookies and login again
- Close unnecessary admin tabs (each has own session)
Performance issues
Slow page load
- Use release build, not debug:
cargo build --release - Check for large images in posts
- Verify server isn’t CPU-constrained
- Enable gzip compression in nginx
Markdown rendering issues
Issue: Code blocks or formatting looks wrong
- Ensure code blocks use triple backticks:
``` - Specify language after backticks:
```rust - Check for syntax errors in markdown
- Use proper HTML entity encoding if needed
Environmental variable errors
Error: “Environment variable not found”
- Ensure .env file exists in project root
- Verify variable names match exactly
- Check file permissions:
chmod 644 .env - Restart server after changing .env
Contributing
Contributions are welcome. Please ensure code follows project patterns and includes appropriate testing.
Support
For issues and questions, please check the troubleshooting section above or review the source code documentation.