A modern Swift web framework with file-based routing and declarative HTML generation
π Live Demo: http://swiftlet.eastlynx.com:8080/
Warning
Development Status: Swiftlets is under active development and is NOT ready for production use. APIs may change, and features may be incomplete. We welcome feedback and contributions to help shape the future of Swift on the web!
Swiftlets is a lightweight, Swift-based web framework that brings the simplicity of file-based routing and the power of type-safe HTML generation to server-side Swift development. Inspired by modern web frameworks, it offers a unique approach where each route is an independent executable module.
- π File-Based Routing - Your file structure defines your routes (
.webbin
files) - π Declarative HTML DSL - SwiftUI-like syntax for type-safe HTML generation
- π― SwiftUI-Style API - Property wrappers (
@Query
,@Cookie
,@Environment
) for easy data access - π§ Zero Configuration - No complex routing tables or configuration files
- π Security First - Source files stay outside the web root, MD5 integrity checks
- β»οΈ Hot Reload - Automatic compilation and reloading during development
- π Cross-Platform - Works on macOS (Intel/Apple Silicon) and Linux (x86_64/ARM64)
Get up and running with Swiftlets in just a few minutes. This guide will walk you through installation, creating your first project, and understanding the basics.
First, ensure you have Swift installed (5.7 or later), then clone the Swiftlets repository and build the server:
# Clone the repository
git clone https://github.com/codelynx/swiftlets.git
cd swiftlets
# Build the server (one time setup)
./build-server
This builds the server binary and places it in the platform-specific directory (e.g., bin/macos/arm64/
).
Before creating your own project, let's explore what Swiftlets can do! The repository includes a complete example site with documentation and component showcases - all built with Swiftlets.
Build and run the example site:
# Build the site
./build-site sites/swiftlets-site
# Run the site
./run-site sites/swiftlets-site
# Or combine build and run
./run-site sites/swiftlets-site --build
Visit http://localhost:8080
and explore:
/showcase
- See all HTML components in action/docs
- Read documentation (also built with Swiftlets!)- View source - Check
sites/swiftlets-site/src/
to see how it's built
π‘ Tip: The entire documentation site you're reading is built with Swiftlets! Check out the source code to see real-world examples.
Swiftlets uses a unique architecture where each route is a standalone executable:
sites/swiftlets-site/
βββ src/ # Swift source files
β βββ index.swift # Homepage route
β βββ about.swift # About page route
β βββ docs/
β βββ index.swift # Docs index route
βββ web/ # Static files + .webbin markers
β βββ styles/ # CSS files
β βββ *.webbin # Route markers (generated)
β βββ images/ # Static assets
βββ bin/ # Compiled executables (generated)
βββ index # Executable for /
βββ about # Executable for /about
βββ docs/
βββ index # Executable for /docs
Key concepts:
- File-based routing: Your file structure defines your routes
- Independent executables: Each route compiles to its own binary
- No Makefiles needed: The build-site script handles everything
- Hot reload ready: Executables can be rebuilt without restarting the server
The build scripts make it easy to work with any site:
# Build a site (incremental - only changed files)
./build-site path/to/site
# Force rebuild all files
./build-site path/to/site --force
# Clean build artifacts
./build-site path/to/site --clean
# Build with static binaries (Linux only, for deployment)
./build-site path/to/site --static
# Run a site
./run-site path/to/site
# Run with custom port
./run-site path/to/site --port 3000
# Build and run in one command
./run-site path/to/site --build
π‘ Tip: The scripts automatically detect your platform (macOS/Linux) and architecture (x86_64/arm64).
- Component Showcase - See all available components
- Study the Source - Learn from real examples
- HTML DSL Guide - Master the SwiftUI-like syntax
Create a simple page using the Swiftlets HTML DSL:
// src/index.swift
import Foundation
import Swiftlets
@main
struct HomePage {
static func main() async throws {
// Parse the request from stdin
let request = try JSONDecoder().decode(Request.self, from: FileHandle.standardInput.readDataToEndOfFile())
let html = Html {
Head {
Title("Welcome to Swiftlets!")
Meta(name: "viewport", content: "width=device-width, initial-scale=1.0")
}
Body {
Container(maxWidth: .large) {
VStack(spacing: 40) {
H1("Hello, Swiftlets! π")
.style("text-align", "center")
.style("margin-top", "3rem")
P("Build modern web apps with Swift")
.style("font-size", "1.25rem")
.style("text-align", "center")
.style("color", "#6c757d")
HStack(spacing: 20) {
Link(href: "/docs/getting-started", "Get Started")
.class("btn btn-primary")
Link(href: "/showcase", "See Examples")
.class("btn btn-outline-secondary")
}
.style("justify-content", "center")
}
}
.style("padding", "3rem 0")
}
}
let response = Response(
status: 200,
headers: ["Content-Type": "text/html; charset=utf-8"],
body: html.render()
)
print(try JSONEncoder().encode(response).base64EncodedString())
}
}
Or use the new SwiftUI-inspired API with property wrappers:
// src/index.swift
import Swiftlets
@main
struct HomePage: SwiftletMain {
@Query("name") var userName: String?
@Cookie("theme") var theme: String?
var title = "Welcome to Swiftlets!"
var meta = ["viewport": "width=device-width, initial-scale=1.0"]
var body: some HTMLElement {
Container(maxWidth: .large) {
VStack(spacing: 40) {
H1("Hello, \(userName ?? "Swiftlets")! π")
.style("text-align", "center")
.style("margin-top", "3rem")
P("Build modern web apps with Swift")
.style("font-size", "1.25rem")
.style("text-align", "center")
.style("color", theme == "dark" ? "#adb5bd" : "#6c757d")
HStack(spacing: 20) {
Link(href: "/docs/getting-started", "Get Started")
.class("btn btn-primary")
Link(href: "/showcase", "See Examples")
.class("btn btn-outline-secondary")
}
.style("justify-content", "center")
}
}
.style("padding", "3rem 0")
}
}
Build and access your page:
# From your project directory
./build-site my-site
./run-site my-site
# Your page is now available at http://localhost:8080/
my-site/
βββ src/ # Swift source files
β βββ index.swift # Home page (/)
β βββ about.swift # About page (/about)
β βββ api/
β βββ users.swift # API endpoint (/api/users)
βββ web/ # Public web root
β βββ *.webbin # Route markers (generated)
β βββ styles/ # CSS files
β βββ scripts/ # JavaScript files
β βββ images/ # Static assets
βββ bin/ # Compiled executables (generated)
βββ Components.swift # Shared components (optional)
Your file structure automatically defines your routes:
src/index.swift
β/
src/about.swift
β/about
src/blog/index.swift
β/blog
src/blog/post.swift
β/blog/post
src/api/users.json.swift
β/api/users.json
Build HTML with Swift's type safety:
VStack(alignment: .center, spacing: .large) {
H1("Welcome")
.id("hero-title")
.classes("display-1")
ForEach(posts) { post in
Article {
H2(post.title)
Paragraph(post.excerpt)
Link("Read more", href: "/blog/\(post.slug)")
}
.classes("mb-4")
}
}
Handle dynamic requests with ease using property wrappers:
import Swiftlets
@main
struct APIHandler: SwiftletMain {
@Query("limit", default: "10") var limit: String?
@Query("offset", default: "0") var offset: String?
@JSONBody<UserFilter>() var filter: UserFilter?
var title = "User API"
var body: ResponseBuilder {
let users = fetchUsers(
limit: Int(limit ?? "10") ?? 10,
offset: Int(offset ?? "0") ?? 0,
filter: filter
)
return ResponseWith {
Pre(try! JSONEncoder().encode(users).string())
}
.contentType("application/json")
.header("X-Total-Count", value: "\(users.count)")
}
}
Or handle cookies and form data:
@main
struct LoginHandler: SwiftletMain {
@FormValue("username") var username: String?
@FormValue("password") var password: String?
@Cookie("session") var existingSession: String?
var title = "Login"
var body: ResponseBuilder {
// Check existing session
if let session = existingSession, validateSession(session) {
return ResponseWith {
Div { H1("Already logged in!") }
}
}
// Handle login
if let user = username, let pass = password {
if authenticate(user, pass) {
let sessionId = createSession(for: user)
return ResponseWith {
Div { H1("Welcome, \(user)!") }
}
.cookie("session", value: sessionId, httpOnly: true)
}
}
// Show login form
return ResponseWith {
Form(action: "/login", method: "POST") {
Input(type: "text", name: "username", placeholder: "Username")
Input(type: "password", name: "password", placeholder: "Password")
Button("Login", type: "submit")
}
}
}
}
- Requirements: macOS 13+, Swift 6.0+
- Architectures: Intel (x86_64) and Apple Silicon (arm64)
- Distributions: Ubuntu 22.04 LTS+
- Architectures: x86_64 and ARM64
- Swift: 5.10+ from swift.org
All build scripts work identically on macOS and Linux. See Ubuntu Scripting Issue for platform-specific considerations.
# Build for current platform
./build-server
# Check Ubuntu prerequisites
./check-ubuntu-prerequisites.sh
# Run the server
./run-server.sh
Visit the showcase site for interactive documentation, or explore these references:
- CLI Reference - Complete CLI documentation
- Routing Guide - Advanced routing patterns
- Configuration - Server configuration
- Architecture - How Swiftlets works
- SwiftUI API Implementation - Complete guide with examples
- SwiftUI API Reference - All property wrappers and protocols
- HTML DSL Reference - All HTML components
- SDK Distribution - Future SDK packaging plans
The best way to learn Swiftlets is by exploring our comprehensive showcase:
# Quick start with the showcase
./build-site sites/swiftlets-site
./run-site sites/swiftlets-site
The showcase includes:
- Component Gallery - All HTML elements with live examples
- Layout Demos - HStack, VStack, Grid, and responsive layouts
- Modifiers Playground - Styling and customization examples
- Interactive Documentation - Learn by doing
- Templates - Project templates for quick start
# Build and run a specific site
./build-site sites/swiftlets-site
./run-site sites/swiftlets-site
# Or build and run in one command
./run-site sites/swiftlets-site --build
swiftlets/
βββ Sources/ # Framework and server source code
βββ bin/ # Platform-specific binaries
β βββ macos/ # macOS binaries (x86_64, arm64)
β βββ linux/ # Linux binaries (x86_64, arm64)
βββ sites/ # Website projects
β βββ swiftlets-site/# Official documentation site
βββ docs/ # Documentation
βββ external/ # External dependencies (Ignite)
βββ sdk/ # SDK distribution
β βββ examples/ # SDK examples
β βββ templates/ # Project templates
βββ articles/ # Blog articles and guides
See Project Structure for details.
We welcome contributions! Please see our Contributing Guide for details.
# Run tests
make test
# Check code style
make lint
# Build documentation
make docs
See our detailed roadmap for more information.
Swiftlets is released under the MIT License. See LICENSE for details.
Testing locally on macOS is straightforward:
# 1. Clone and build Swiftlets
git clone https://github.com/codelynx/swiftlets.git
cd swiftlets
./build-server
# 2. Build and run the showcase site
./build-site sites/swiftlets-site
./run-site sites/swiftlets-site
# Visit http://localhost:8080
# 3. For development with auto-rebuild
./run-site sites/swiftlets-site --build --port 3000
Deploy your Swiftlets application to AWS EC2 with our Docker-based build process:
- AWS account with EC2 access
- Docker installed locally (for cross-compilation)
- SSH key pair for EC2
- Domain name (optional, for custom DNS)
-
Launch EC2 Instance
- Recommended: t4g.small (ARM64, 2GB RAM) - best price/performance
- OS: Ubuntu 24.04 LTS
- Security Group: Open ports 22 (SSH), 8080 (HTTP)
-
Setup EC2 Instance
# Connect to your EC2 ssh -i ~/.ssh/your-key.pem ubuntu@<YOUR-EC2-IP> # Run setup script (installs Swift, Nginx, creates swap) curl -fsSL https://raw.githubusercontent.com/codelynx/swiftlets/main/deploy/ec2/setup-instance-ubuntu24.sh | bash
-
Build Linux Executables (Local)
# From your local machine (in swiftlets directory) # Build all executables using Docker ./deploy/docker-build-deploy/direct-docker-build.sh # This creates Linux ARM64 executables in sites/swiftlets-site/bin/
-
Deploy to EC2
# Set your EC2 details export EC2_HOST="<YOUR-EC2-IP>" export KEY_FILE="~/.ssh/your-key.pem" # Deploy ./deploy/deploy-swiftlets-site.sh
-
Access Your Site
- URL:
http://<YOUR-EC2-IP>:8080
- Or with custom domain:
http://yoursite.com:8080
- URL:
- Auto-restart: Systemd service ensures the server stays running
- Nginx proxy: Handles incoming requests on port 8080
- Process isolation: Each route runs as separate executable
- Easy updates: Just rebuild and redeploy executables
Configure custom domain with Route 53:
# Create A record pointing to your EC2 IP
aws route53 change-resource-record-sets \
--hosted-zone-id <YOUR-ZONE-ID> \
--change-batch '{
"Changes": [{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "swiftlet.yourdomain.com",
"Type": "A",
"TTL": 300,
"ResourceRecords": [{"Value": "<YOUR-EC2-IP>"}]
}
}]
}'
# Check service status
sudo systemctl status swiftlets
# View logs
sudo journalctl -u swiftlets -f
# Restart service
sudo systemctl restart swiftlets
# Update deployment
./deploy/deploy-swiftlets-site.sh # Just run again
For detailed deployment documentation, see:
- AWS EC2 Deployment Guide
- Production Deployment Guide
- Static Binary Deployment - Deploy without Swift runtime
For containerized deployments:
# Build Docker image
docker build -t swiftlets .
# Run container
docker run -d -p 8080:8080 --name swiftlets-app swiftlets
Build self-contained executables that don't require Swift runtime on the server:
# Setup Docker buildx for native ARM64 builds
./deploy/docker/setup-buildx.sh
# Build static binaries for EC2
./deploy/docker/build-for-ec2.sh swiftlets-site
# Extract static binaries
docker create --name extract swiftlets-ec2-static
docker cp extract:/app/bin ./static-bin
docker rm extract
The extracted binaries are fully static and can run on any Linux ARM64 system without Swift installed. This approach, inspired by Vapor's --static-swift-stdlib
flag, simplifies deployment significantly.
See Static Binary Deployment Guide for details.
- Only one file builds on Linux: This is a known bash loop issue. See Ubuntu Scripting Issue for details.
- MD5 command not found: The scripts automatically handle differences between macOS (md5) and Linux (md5sum).
- Build errors: Use
--verbose
flag for detailed output:./build-site sites/your-site --verbose
- EC2 deployment fails: Ensure Docker is installed locally and EC2 has sufficient resources (2GB+ RAM)
For more troubleshooting help, see the documentation.
- Inspired by Ignite by Paul Hudson
- Built with Swift and β€οΈ