import PrismServerimport Foundation// Create serverlet server = PrismHTTPServer(port: 8080)// Open SQLite database (file-based for persistence)let db = try PrismDatabase(path: "bookmarks.db")// Run migrationslet migrator = PrismMigrator(database: db)try await migrator.migrate([ PrismMigration( name: "create_bookmarks", up: """ CREATE TABLE bookmarks ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, url TEXT NOT NULL, tags TEXT DEFAULT '', created_at TEXT DEFAULT (datetime('now')) ) """, down: "DROP TABLE bookmarks" )])
Use ":memory:" instead of "bookmarks.db" for an in-memory database during development. It resets every restart — great for testing.
// Middleware runs on every request, in orderawait server.use(PrismCORSMiddleware()) // Allow cross-origin requestsawait server.use(PrismLoggingMiddleware()) // Log every request with timingawait server.use(PrismErrorMiddleware(includeStackTrace: true)) // Catch errors globally
Middleware is applied in the order you register it. The logging middleware wraps everything after it, so it captures the total response time including CORS processing.
await server.get("/bookmarks") { request in let rows = try await db.query("SELECT * FROM bookmarks ORDER BY created_at DESC") let bookmarks = rows.map { row -> [String: String] in [ "id": "\(row["id"]!)", "title": "\(row["title"]!)", "url": "\(row["url"]!)", "tags": "\(row["tags"]!)", "created_at": "\(row["created_at"]!)" ] } return .json(bookmarks)}
Every route handler receives a PrismHTTPRequest and returns a PrismHTTPResponse. The .json() factory serializes any Encodable value and sets the right headers.
await server.post("/bookmarks") { request in // Validate input let validation = request.validateJSON { v in v.field("title", .required, .minLength(1), .maxLength(200)) v.field("url", .required, .url) } // Return 422 with field-level errors if invalid if let errorResponse = validation.errorResponse() { return errorResponse } // Parse body guard let body = request.body, let json = try? JSONSerialization.jsonObject(with: body) as? [String: Any] else { return PrismHTTPResponse(status: .badRequest, body: .text("Invalid JSON")) } let title = json["title"] as? String ?? "" let url = json["url"] as? String ?? "" let tags = json["tags"] as? String ?? "" // Insert into database try await db.execute( "INSERT INTO bookmarks (title, url, tags) VALUES (?, ?, ?)", parameters: [.text(title), .text(url), .text(tags)] ) let id = await db.lastInsertID return .json(["id": id, "created": true], status: .created)}
Validation rules compose naturally. .required ensures the field is present, .url checks for valid http:// or https:// format. If validation fails, errorResponse() returns a structured JSON error — you never need to format error messages manually.
await server.get("/bookmarks/:id") { request in let id = request.parameters["id"]! guard let row = try await db.queryFirst( "SELECT * FROM bookmarks WHERE id = ?", parameters: [.text(id)] ) else { throw PrismAppError.notFound("Bookmark not found") } return .json([ "id": "\(row["id"]!)", "title": "\(row["title"]!)", "url": "\(row["url"]!)", "tags": "\(row["tags"]!)", "created_at": "\(row["created_at"]!)" ])}
Route parameters (:id) are extracted automatically and available in request.parameters. The PrismAppError.notFound() throw is caught by PrismErrorMiddleware and converted to a clean 404 JSON response.
await server.delete("/bookmarks/:id") { request in let id = request.parameters["id"]! let changes = try await db.execute( "DELETE FROM bookmarks WHERE id = ?", parameters: [.text(id)] ) guard changes > 0 else { throw PrismAppError.notFound("Bookmark not found") } return .json(["deleted": true])}