# Building HTTP APIs
This guide shows you how to create web APIs with Clean Language and Clean Node Server.
## The Basics
Your Clean Language code can register routes that respond to HTTP requests. Here's the pattern:
```clean
function handleSomething(): string {
// Do something and return a response
return _http_json('{"message": "Hello!"}')
}
function main(): void {
_http_route("GET", "/something", handleSomething)
_http_listen(3000)
}
```
## Registering Routes
Use `_http_route` to tell the server which function handles which URL:
```clean
// Basic routes
_http_route("GET", "/users", handleListUsers)
_http_route("POST", "/users", handleCreateUser)
_http_route("GET", "/users/:id", handleGetUser)
_http_route("PUT", "/users/:id", handleUpdateUser)
_http_route("DELETE", "/users/:id", handleDeleteUser)
```
### URL Parameters
Use `:paramName` in your route path to capture parts of the URL:
```clean
// Route: GET /users/:id
function handleGetUser(): string {
string userId = _req_param("id")
// userId contains whatever was in the URL
// GET /users/42 -> userId = "42"
return _http_json('{"id": "' + userId + '"}')
}
```
### Getting Parameters as Numbers
If you need the parameter as an integer:
```clean
function handleGetUser(): string {
integer userId = _req_param_int("id")
// Now you can do math with it!
return _http_json('{"id": ' + int_to_string(userId) + '}')
}
```
## Reading Request Data
### Query String Parameters
For URLs like `/search?q=hello&limit=10`:
```clean
function handleSearch(): string {
string query = _req_query("q") // "hello"
string limit = _req_query("limit") // "10"
return _http_json('{"query": "' + query + '"}')
}
```
### Request Body
For POST/PUT requests with a body:
```clean
function handleCreateUser(): string {
// Get the entire body as a string
string body = _req_body()
// Or get the body as parsed JSON (if it's valid JSON)
string jsonBody = _req_json()
return _http_json('{"received": true}')
}
```
### Individual Fields from JSON Body
Extract specific fields from a JSON request body:
```clean
function handleLogin(): string {
string email = _req_body_field("email")
string password = _req_body_field("password")
// Now use email and password...
return _http_json('{"user": "' + email + '"}')
}
```
### Request Headers
```clean
function handleRequest(): string {
string authHeader = _req_header("Authorization")
string contentType = _req_header("Content-Type")
string userAgent = _req_header("User-Agent")
return _http_json('{"ok": true}')
}
```
### Cookies
```clean
function handleRequest(): string {
string sessionId = _req_cookie("session_id")
return _http_json('{"hasSession": true}')
}
```
### Other Request Info
```clean
function handleRequest(): string {
string method = _req_method() // "GET", "POST", etc.
string path = _req_path() // "/users/42"
return _http_json('{"method": "' + method + '"}')
}
```
## Sending Responses
### JSON Responses (Most Common)
```clean
function handleApi(): string {
return _http_json('{"status": "ok", "data": [1, 2, 3]}')
}
```
This automatically sets `Content-Type: application/json`.
### HTML Responses
```clean
function handlePage(): string {
return _http_html('
Hello!
')
}
```
### Plain Text Responses
```clean
function handleText(): string {
return _http_text('Just some plain text')
}
```
### Custom Status and Headers
For more control over the response:
```clean
function handleCustom(): string {
_http_set_status(201) // Created
_http_set_header("X-Custom-Header", "my-value")
_http_set_body('{"created": true}')
return ""
}
```
### Combined Response (Status + Content-Type + Body)
```clean
function handleRespond(): string {
return _http_respond(200, "application/json", '{"ok": true}')
}
```
## Error Responses
Built-in helpers for common error responses:
```clean
function handleNotFound(): string {
return _http_not_found("User not found")
}
function handleBadInput(): string {
return _http_bad_request("Email is required")
}
function handleUnauthorized(): string {
return _http_unauthorized("Please log in")
}
function handleForbidden(): string {
return _http_forbidden("You don't have permission")
}
function handleServerError(): string {
return _http_server_error("Something went wrong")
}
```
Each of these returns a JSON response like:
```json
{
"ok": false,
"err": {
"code": "NOT_FOUND",
"message": "User not found"
}
}
```
## Redirects
Send users to another URL:
```clean
function handleOldUrl(): string {
// Temporary redirect (302)
return _http_redirect("/new-url", 0)
}
function handleMovedPermanently(): string {
// Permanent redirect (301)
return _http_redirect("/new-url", 1)
}
```
## Protected Routes
For routes that require authentication:
```clean
function main(): void {
// Anyone can access this
_http_route("GET", "/public", handlePublic)
// Must be logged in
_http_route_protected("GET", "/profile", handleProfile, "")
// Must be an admin
_http_route_protected("GET", "/admin", handleAdmin, "admin")
_http_listen(3000)
}
```
The server automatically checks authentication and returns 401/403 errors if needed.
## Complete Example: A User API
```clean
// List all users
function handleListUsers(): string {
string result = _db_query("SELECT id, name, email FROM users", "[]")
return _http_json(result)
}
// Get one user by ID
function handleGetUser(): string {
integer id = _req_param_int("id")
string params = '[' + int_to_string(id) + ']'
string result = _db_query("SELECT * FROM users WHERE id = $1", params)
if (result == "[]") {
return _http_not_found("User not found")
}
return _http_json(result)
}
// Create a new user
function handleCreateUser(): string {
string name = _req_body_field("name")
string email = _req_body_field("email")
if (name == "" || email == "") {
return _http_bad_request("Name and email are required")
}
string params = '["' + name + '", "' + email + '"]'
string result = _db_execute(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id",
params
)
_http_set_status(201)
return _http_json('{"id": ' + result + ', "name": "' + name + '"}')
}
// Set up routes
function main(): void {
_http_route("GET", "/users", handleListUsers)
_http_route("GET", "/users/:id", handleGetUser)
_http_route("POST", "/users", handleCreateUser)
_http_listen(3000)
print("User API running on port 3000")
}
```
## Tips
1. **Always return something** - Every handler should return a string (even if empty)
2. **Use JSON for APIs** - It's the standard and works great with `_http_json`
3. **Validate input** - Check that required fields are present before using them
4. **Use meaningful error messages** - Help your API users understand what went wrong
5. **Log in verbose mode** - Run with `--verbose` during development to see all requests
## Next Steps
- [Database Guide](database.md) - Store and retrieve data
- [Authentication Guide](authentication.md) - Add login and security
- [Functions Reference](functions-reference.md) - All available functions