What kinda routes can I use with Hummingbird?
This article is part of a series.
I mean…all of them? So this is all based on walking through the Router Guide, but I wanted one project that served up the sampler.
Here is a buildRouter function with a smorgasbord of examples.
In context at: https://github.com/carlynorama/HummingbirdExamples/tree/main/02_basic_responses
buildRouter
func buildRouter() -> Router<AppRequestContext> {
let router = Router(context: AppRequestContext.self)
router.addMiddleware {
// logging middleware
LogRequestsMiddleware(.info)
//serves the static files in public folder by default.
FileMiddleware(searchForIndexHtml: true)
}
// SIMPLE ROUTING
// These three types have been given ResponseGenerator status by Hummingbird
// - HTTPResponseStatus
// - String
// - Optional
// The closure takes in a request and a context and returns
// some ResponseGenerator
router.get("/ping") { _, _ -> HTTPResponse.Status in
return .ok
}
router.get("/hello") { request, _ -> String in
return "Well Hello!\n\n\(request)"
}
// full call
router.on("/goodbye", method: .get) { request, context in
return "Ciao!"
}
//---- WILDCARDS
// https://docs.hummingbird.codes/2.0/documentation/hummingbird/routerguide#Wildcards
router.get("/files/*") { request, _ in
return request.uri.description
}
//note: ** is 1 or more, not zero or more. (2025/08)
router.get("/doublewild/**") { _, context in
return context.parameters.getCatchAll().joined(separator: "/")
}
//---- Parameter Capture
// //https://docs.hummingbird.codes/2.0/documentation/hummingbird/routerguide#Parameter-Capture
router.get("/user/:id") { _, context in
guard let id = context.parameters.get("id", as: Int.self) else {
throw HTTPError(.badRequest)
}
return "\(id)"
}
router.get("/user/:id/:name") { _, context in
guard let id = context.parameters.get("id", as: Int.self) else {
throw HTTPError(.badRequest)
}
guard let name = context.parameters.get("name", as: String.self) else {
throw HTTPError(.badRequest)
}
return "found \(name) for \(id)"
}
router.get("/img/{image}.jpg") { _, context in
guard let imageName = context.parameters.get("image") else {
throw HTTPError(.badRequest)
}
switch imageName {
case "bunny":
return " \\\n \\ /\\\n ( )\n.( o )."
// \
// \ /\
// ( )
// .( o ).
default: return "this is a \(imageName) "
}
}
router.get("/query") { request, _ in
// extract parameter from URL of form /query?id={userId}&message={message}
guard let id = request.uri.queryParameters.get("id", as: Double.self) else {
throw HTTPError(.badRequest, message: "id?")
}
guard let message = request.uri.queryParameters.get("message", as: String.self) else {
throw HTTPError(.badRequest, message: "message?")
}
return "tell \(id) I said \"\(message)\""
}
// GROUPS
//if one has a logical group one can batch them together.
router.group("/circus")
.get(use: { _, _ in "if I had a list of circus acts I would show a list of circus acts" })
.get("peanuts") { _, _ in "yummy peanuts" }
.group("clowns")
.get { _, _ in "if I had a list of clowns I would show a list of clowns" }
.get(
"{id}",
use: { _, context in
"that's the clown called \(context.parameters.get("id") ?? "no ID")!"
})
struct MiniCodable: Decodable, ResponseEncodable {
let number: Int
let phrase: String?
}
//the default encoder is JSON
router.get("/encodable") { _, _ -> MiniCodable in
return MiniCodable(number: 5, phrase: "hello!")
}
router.group("/decodable")
.get { _, _ in "this was a GET. Please submit some info using POST" }
//curl -i -X POST localhost:8080/decodable/default -d'{"number":12,"phrase":"are you going to go my way"}'
.post("default") { request, context in
let decoded = try await request.decode(as: MiniCodable.self, context: context)
print("DECODED: \(decoded)")
return "DECODED: \(decoded)" //also consider an HTTPResponseStatus
}
//curl -i -X POST 'http://localhost:8080/decodable/form' -H 'Content-Type: application/x-www-form-urlencoded' --data-raw 'number=36&phrase=message%20string'
.post("form") { request, context in
print(request.headers)
let decoded = try await URLEncodedFormDecoder().decode(
MiniCodable.self, from: request, context: context)
print("DECODED: \(decoded)")
return "DECODED: \(decoded)"
}
return router
}
Testing Script
To test them all here’s a build script with curl calls.
#!/bin/sh
MY_PORT="8080"
separate () {
sleep 2s
echo -e "\n---------------------\n"
}
curl -i "http://localhost:$MY_PORT/ping"
separate
curl -i "http://localhost:$MY_PORT/hello"
separate
curl -i "http://localhost:$MY_PORT/goodbye"
separate
curl -i "http://localhost:$MY_PORT/files/putonethingafter"
separate
curl -i "http://localhost:$MY_PORT/doublewild/put/anything/I/want"
separate
curl -i "http://localhost:$MY_PORT/user/8675309"
separate
curl -i "http://localhost:$MY_PORT/user/8675309/Jenny"
separate
curl -i "http://localhost:$MY_PORT/img/non-bunny.jpg"
separate
curl -i "http://localhost:$MY_PORT/img/bunny.jpg"
separate
curl -i "http://localhost:$MY_PORT/query?id=35.53&message=hello%20with%20spaces"
separate
curl -i "http://localhost:$MY_PORT/circus"
separate
curl -i "http://localhost:$MY_PORT/circus/peanuts"
separate
curl -i "http://localhost:$MY_PORT/circus/clowns"
separate
curl -i "http://localhost:$MY_PORT/circus/clowns/anythingcanbeanidforthis"
separate
curl -i "http://localhost:$MY_PORT/encodable"
separate
curl -i -X POST localhost:$MY_PORT/decodable/default -d'{"number":12,"phrase":"are you going to go my way"}'
separate
curl -i -X POST localhost:$MY_PORT/decodable/default -d'{"number":14}'
separate
curl -i -X POST 'http://localhost:8080/decodable/form' -H 'Content-Type: application/x-www-form-urlencoded' --data-raw 'number=36&phrase=message%20string'
separate
curl -i -X POST 'http://localhost:8080/decodable/form' -H 'Content-Type: application/x-www-form-urlencoded' --data-raw 'number=36&phrase'