Skip to main content
SQLPage uses a file-based routing system that maps HTTP request URLs directly to .sql files in your web root directory. This approach is similar to traditional web servers but specifically designed for SQL-driven applications.

Basic Routing Rules

The routing system follows a precedence order to determine which file to execute:
./
├── index.sql
├── users.sql
├── api/
│   ├── index.sql
│   └── data.sql
├── about.sql
└── 404.sql

URL to File Resolution

1. Root Path (/)

The root URL always maps to index.sql:
GET / HTTP/1.1
→ Executes: index.sql
index.sql is the entry point of your SQLPage application. This is where you typically define your homepage.

2. Paths with .sql Extension

Direct mapping to the SQL file:
GET /users.sql HTTP/1.1
→ Executes: users.sql

GET /admin/dashboard.sql HTTP/1.1
→ Executes: admin/dashboard.sql

3. Paths without Extension

SQLPage looks for {path}.sql:
GET /users HTTP/1.1
→ Looks for: users.sql
→ If found: Executes users.sql
→ If not found: Check for users/index.sql
1

Try {path}.sql

First attempt: Execute users.sql if it exists
2

Check for directory

If users/index.sql exists, redirect to /users/
3

Fall back to 404

If neither exists, look for custom 404 handler

4. Directory Paths (Trailing Slash)

Paths ending with / look for index.sql in that directory:
GET /admin/ HTTP/1.1
→ Executes: admin/index.sql

GET /api/v1/ HTTP/1.1
→ Executes: api/v1/index.sql

5. Static Assets

Non-SQL files are served as static assets:
GET /favicon.ico HTTP/1.1
→ Serves: favicon.ico (if exists)
→ Falls back to: favicon.ico.sql (if exists)
→ Otherwise: 404 handler

GET /styles/app.css HTTP/1.1
→ Serves: styles/app.css
If both file.ext and file.ext.sql exist, SQLPage prefers the static file. To force SQL execution, request file.ext.sql explicitly.

Complete Routing Flow

Here’s the complete decision tree for routing:

Custom 404 Error Pages

SQLPage supports hierarchical custom 404 handlers:
./
├── index.sql
├── 404.sql              # Root-level 404
├── users.sql
└── admin/
    ├── index.sql
    ├── 404.sql          # Admin-specific 404
    └── users.sql

404 Handler Walk-up

When a file is not found, SQLPage walks up the directory tree:
1

Check current directory

Look for 404.sql in the same directory as the requested path
2

Check parent directory

If not found, check parent directory for 404.sql
3

Repeat until root

Continue walking up until reaching the web root
4

Default 404

If no custom 404 found anywhere, return default error page

Example Custom 404 Handler

404.sql
-- Set 404 status code
SELECT 'http_header' as component,
       'Status' as header,
       '404 Not Found' as value;

SELECT 'hero' as component,
       'Page Not Found' as title,
       'The page you requested does not exist.' as description,
       'Sorry about that!' as description_md;

SELECT 'button' as component;
SELECT '/' as link,
       'Go Home' as title,
       'home' as icon;

-- Log 404 for analytics
INSERT INTO page_views (path, status_code, timestamp)
VALUES ($path, 404, CURRENT_TIMESTAMP);

Query Parameters

Query strings are preserved and accessible via SQL parameters:
GET /users?id=42&filter=active HTTP/1.1
users.sql
SELECT 'card' as component;

SELECT 
    name as title,
    email as description
FROM users
WHERE id = $id           -- Gets value '42'
  AND status = $filter;  -- Gets value 'active'
Query parameters are available via $param syntax. See SQL Parameters for details.

Site Prefix Configuration

Deploy SQLPage under a subpath using site_prefix:
{
  "site_prefix": "/app"
}
Effect on routing:
# Without site_prefix
GET /users → users.sql

# With site_prefix = "/app"
GET /app/users → users.sql
GET /users → 301 Redirect to /app/users
Use site_prefix when running SQLPage behind a reverse proxy under a subpath, like /app or /admin.

Routing Examples

Blog with Categories

Directory Structure
blog/
├── index.sql           # List all posts
├── post.sql            # Single post view
├── category/
│   ├── index.sql       # List categories
│   └── view.sql        # Posts in category
└── 404.sql
URLs
GET /blog/              → blog/index.sql
GET /blog/post?id=1     → blog/post.sql (with $id = 1)
GET /blog/category/     → blog/category/index.sql
GET /blog/category/view?name=tech → blog/category/view.sql

REST-like API Structure

Directory Structure
api/
├── users.sql           # GET/POST users
├── user.sql            # GET/PUT/DELETE single user
├── v2/
│   ├── users.sql
│   └── user.sql
└── 404.sql
URLs
GET  /api/users              → api/users.sql
POST /api/users              → api/users.sql (different logic)
GET  /api/user?id=5          → api/user.sql
GET  /api/v2/users           → api/v2/users.sql
Handle different HTTP methods in SQL using WHERE $method = 'POST' or similar conditions.

Advanced Routing Patterns

Dynamic Routing with Parameters

Simulate dynamic routes using query parameters:
blog/post.sql
-- URL: /blog/post?slug=my-first-post

SELECT 'http_header' as component,
       'Status' as header,
       CASE 
           WHEN $slug IS NULL THEN '400 Bad Request'
           ELSE '200 OK'
       END as value;

SELECT 'article' as component;

SELECT 
    title,
    content,
    published_date
FROM blog_posts
WHERE slug = $slug;

-- If no post found, show 404
SELECT 'redirect' as component,
       '/blog/404' as link
WHERE NOT EXISTS (
    SELECT 1 FROM blog_posts WHERE slug = $slug
);

Locale-Specific Routing

Directory Structure
./
├── index.sql
├── en/
│   ├── index.sql
│   └── about.sql
├── fr/
│   ├── index.sql
│   └── about.sql
└── detect_language.sql
detect_language.sql
-- Redirect based on Accept-Language header
SELECT 'redirect' as component,
       CASE 
           WHEN $http_accept_language LIKE '%fr%' THEN '/fr/'
           ELSE '/en/'
       END as link;

Routing Best Practices

  • Use descriptive filenames: user_profile.sql, not up.sql
  • Mirror your app’s logical structure in directories
  • Avoid deeply nested directories (3-4 levels max)
  • Always provide a root-level 404.sql
  • Validate required parameters early in SQL files
  • Return appropriate HTTP status codes
  • Never expose sensitive files (.env, config files)
  • Use authentication component to protect routes
  • Validate and sanitize all parameters
  • Prefer /products over /products.sql
  • Use descriptive slugs: /blog/post?slug=intro-to-sqlpage
  • Implement canonical URLs for duplicate content

Next Steps

Components

Learn how to render UI components

SQL Parameters

Master parameter handling

Authentication

Protect routes with authentication

Configuration

Configure routing and site settings

Build docs developers (and LLMs) love