Skip to main content

API Routes

API routes in Luat are created using +server.lua files. These files handle HTTP requests and return JSON responses, making it easy to build APIs alongside your pages.

Basic API Route

Create a +server.lua file in your routes directory:

-- src/routes/api/hello/+server.lua

function GET(ctx)
return {
status = 200,
body = {
message = "Hello, World!"
}
}
end

This creates an endpoint at /api/hello that responds to GET requests.

HTTP Methods

Export functions named after HTTP methods to handle different request types:

-- src/routes/api/users/+server.lua

-- GET /api/users - List all users
function GET(ctx)
return {
status = 200,
body = {
users = {
{ id = 1, name = "Alice" },
{ id = 2, name = "Bob" }
}
}
}
end

-- POST /api/users - Create a new user
function POST(ctx)
local data = ctx.body
-- Create user logic here
return {
status = 201,
body = {
id = 3,
name = data.name,
created = true
}
}
end

-- PUT /api/users - Update a user
function PUT(ctx)
local data = ctx.body
return {
status = 200,
body = { updated = true }
}
end

-- DELETE /api/users - Delete a user
function DELETE(ctx)
return {
status = 204,
body = nil
}
end

-- PATCH /api/users - Partially update a user
function PATCH(ctx)
return {
status = 200,
body = { patched = true }
}
end

Response Format

API route functions return a table with:

FieldTypeRequiredDescription
statusnumberYesHTTP status code
bodytable/nilNoResponse body (auto-serialized to JSON)
headerstableNoAdditional response headers

Setting Custom Headers

function GET(ctx)
return {
status = 200,
body = { data = "example" },
headers = {
["Cache-Control"] = "max-age=3600",
["X-Custom-Header"] = "value"
}
}
end

Context Object

The ctx parameter provides access to request information:

function GET(ctx)
-- URL parameters from dynamic routes
local userId = ctx.params.id

-- Query string parameters (?name=value)
local search = ctx.query.search

-- Request headers
local authHeader = ctx.headers["Authorization"]

-- Request URL
local url = ctx.url

return {
status = 200,
body = { userId = userId }
}
end

function POST(ctx)
-- Request body (parsed JSON)
local data = ctx.body
local name = data.name
local email = data.email

return {
status = 201,
body = { received = data }
}
end

Dynamic API Routes

Use dynamic route parameters for resource-specific endpoints:

-- src/routes/api/users/[id]/+server.lua

function GET(ctx)
local userId = ctx.params.id

-- Fetch user by ID
local user = { id = userId, name = "User " .. userId }

if not user then
return {
status = 404,
body = { error = "User not found" }
}
end

return {
status = 200,
body = user
}
end

function DELETE(ctx)
local userId = ctx.params.id

-- Delete user logic

return {
status = 204,
body = nil
}
end

Error Handling

Return appropriate status codes for errors:

function GET(ctx)
local id = ctx.params.id

-- Validate input
if not id or id == "" then
return {
status = 400,
body = { error = "ID is required" }
}
end

-- Check authorization
local token = ctx.headers["Authorization"]
if not token then
return {
status = 401,
body = { error = "Unauthorized" }
}
end

-- Resource not found
local resource = findResource(id)
if not resource then
return {
status = 404,
body = { error = "Resource not found" }
}
end

-- Server error
local success, err = pcall(function()
-- risky operation
end)
if not success then
return {
status = 500,
body = { error = "Internal server error" }
}
end

return {
status = 200,
body = resource
}
end

Common Status Codes

CodeMeaningUse Case
200OKSuccessful GET, PUT, PATCH
201CreatedSuccessful POST creating a resource
204No ContentSuccessful DELETE
400Bad RequestInvalid input
401UnauthorizedMissing/invalid authentication
403ForbiddenAuthenticated but not allowed
404Not FoundResource doesn't exist
500Server ErrorUnexpected errors

API Route Organization

Organize your API routes logically:

src/routes/api/
├── auth/
│ ├── login/+server.lua # POST /api/auth/login
│ └── logout/+server.lua # POST /api/auth/logout
├── users/
│ ├── +server.lua # GET, POST /api/users
│ └── [id]/
│ └── +server.lua # GET, PUT, DELETE /api/users/:id
└── posts/
├── +server.lua # GET, POST /api/posts
└── [slug]/
└── +server.lua # GET, PUT, DELETE /api/posts/:slug