콘텐츠로 이동

Routing

Routing is the process of finding the appropriate request handler for an incoming request. At the core of Vapor's routing is a high-performance, trie-node router from RoutingKit.

Overview

To understand how routing works in Vapor, you should first understand a few basics about HTTP requests. Take a look at the following example request.

GET /hello/vapor HTTP/1.1
host: vapor.codes
content-length: 0

This is a simple GET HTTP request to the URL /hello/vapor. This is the kind of HTTP request your browser would make if you pointed it to the following URL.

http://vapor.codes/hello/vapor

HTTP Method

The first part of the request is the HTTP method. GET is the most common HTTP method, but there are several you will use often. These HTTP methods are often associated with CRUD semantics.

Method CRUD
GET Read
POST Create
PUT Replace
PATCH Update
DELETE Delete

Request Path

Right after the HTTP method is the request's URI. This consists of a path starting with / and an optional query string after ?. The HTTP method and path are what Vapor uses to route requests.

After the URI is the HTTP version followed by zero or more headers and finally a body. Since this is a GET request, it does not have a body.

Router Methods

Let's take a look at how this request could be handled in Vapor.

app.get("hello", "vapor") { req in 
    return "Hello, vapor!"
}

All of the common HTTP methods are available as methods on Application. They accept one or more string arguments that represent the request's path separated by /.

Note that you could also write this using on followed by the method.

app.on(.GET, "hello", "vapor") { ... }

With this route registered, the example HTTP request from above will result in the following HTTP response.

HTTP/1.1 200 OK
content-length: 13
content-type: text/plain; charset=utf-8

Hello, vapor!

Route Parameters

Now that we've successfully routed a request based on the HTTP method and path, let's try making the path dynamic. Notice that the name "vapor" is hardcoded in both the path and the response. Let's make this dynamic so that you can visit /hello/<any name> and get a response.

app.get("hello", ":name") { req -> String in
    let name = req.parameters.get("name")!
    return "Hello, \(name)!"
}

By using a path component prefixed with :, we indicate to the router that this is a dynamic component. Any string supplied here will now match this route. We can then use req.parameters to access the value of the string.

If you run the example request again, you'll still get a response that says hello to vapor. However, you can now include any name after /hello/ and see it included in the response. Let's try /hello/swift.

GET /hello/swift HTTP/1.1
content-length: 0
HTTP/1.1 200 OK
content-length: 13
content-type: text/plain; charset=utf-8

Hello, swift!

Now that you understand the basics, check out each section to learn more about parameters, groups, and more.

Routes

A route specifies a request handler for a given HTTP method and URI path. It can also store additional metadata.

Methods

Routes can be registered directly to your Application using various HTTP method helpers.

// responds to GET /foo/bar/baz
app.get("foo", "bar", "baz") { req in
    ...
}

Route handlers support returning anything that is ResponseEncodable. This includes Content, an async closure, and any EventLoopFutures where the future value is ResponseEncodable.

You can specify the return type of a route using -> T before in. This can be useful in situations where the compiler cannot determine the return type.

app.get("foo") { req -> String in
    return "bar"
}

These are the supported route helper methods:

  • get
  • post
  • patch
  • put
  • delete

In addition to the HTTP method helpers, there is an on function that accepts HTTP method as an input parameter.

// responds to OPTIONS /foo/bar/baz
app.on(.OPTIONS, "foo", "bar", "baz") { req in
    ...
}

Path Component

Each route registration method accepts a variadic list of PathComponent. This type is expressible by string literal and has four cases:

  • Constant (foo)
  • Parameter (:foo)
  • Anything (*)
  • Catchall (**)

Constant

This is a static route component. Only requests with an exactly matching string at this position will be permitted.

// responds to GET /foo/bar/baz
app.get("foo", "bar", "baz") { req in
    ...
}

Parameter

This is a dynamic route component. Any string at this position will be allowed. A parameter path component is specified with a : prefix. The string following the : will be used as the parameter's name. You can use the name to later fetch the parameters value from the request.

// responds to GET /foo/bar/baz
// responds to GET /foo/qux/baz
// ...
app.get("foo", ":bar", "baz") { req in
    ...
}

Anything

This is very similar to parameter except the value is discarded. This path component is specified as just *.

// responds to GET /foo/bar/baz
// responds to GET /foo/qux/baz
// ...
app.get("foo", "*", "baz") { req in
    ...
}

Catchall

This is a dynamic route component that matches one or more components. It is specified using just **. Any string at this position or later positions will be matched in the request.

// responds to GET /foo/bar
// responds to GET /foo/bar/baz
// ...
app.get("foo", "**") { req in 
    ...
}

Parameters

When using a parameter path component (prefixed with :), the value of the URI at that position will be stored in req.parameters. You can use the name of the path component to access the value.

// responds to GET /hello/foo
// responds to GET /hello/bar
// ...
app.get("hello", ":name") { req -> String in
    let name = req.parameters.get("name")!
    return "Hello, \(name)!"
}

Tip

We can be sure that req.parameters.get will never return nil here since our route path includes :name. However, if you are accessing route parameters in middleware or in code triggered by multiple routes, you will want to handle the possibility of nil.

Tip

If you want to retrieve URL query params, e.g. /hello/?name=foo you need to use Vapor's Content APIs to handle URL encoded data in the URL's query string. See Content reference for more details.

req.parameters.get also supports casting the parameter to LosslessStringConvertible types automatically.

// responds to GET /number/42
// responds to GET /number/1337
// ...
app.get("number", ":x") { req -> String in 
    guard let int = req.parameters.get("x", as: Int.self) else {
        throw Abort(.badRequest)
    }
    return "\(int) is a great number"
}

The values of the URI matched by Catchall (**) will be stored in req.parameters as [String]. You can use req.parameters.getCatchall to access those components.

// responds to GET /hello/foo
// responds to GET /hello/foo/bar
// ...
app.get("hello", "**") { req -> String in
    let name = req.parameters.getCatchall().joined(separator: " ")
    return "Hello, \(name)!"
}

Body Streaming

When registering a route using the on method, you can specify how the request body should be handled. By default, request bodies are collected into memory before calling your handler. This is useful since it allows for request content decoding to be synchronous even though your application reads incoming requests asynchronously.

By default, Vapor will limit streaming body collection to 16KB in size. You can configure this using app.routes.

// Increases the streaming body collection limit to 500kb
app.routes.defaultMaxBodySize = "500kb"

If a streaming body being collected exceeds the configured limit, a 413 Payload Too Large error will be thrown.

To configure request body collection strategy for an individual route, use the body parameter.

// Collects streaming bodies (up to 1mb in size) before calling this route.
app.on(.POST, "listings", body: .collect(maxSize: "1mb")) { req in
    // Handle request. 
}

If a maxSize is passed to collect, it will override the application's default for that route. To use the application's default, omit the maxSize argument.

For large requests, like file uploads, collecting the request body in a buffer can potentially strain your system memory. To prevent the request body from being collected, use the stream strategy.

// Request body will not be collected into a buffer.
app.on(.POST, "upload", body: .stream) { req in
    ...
}

When the request body is streamed, req.body.data will be nil. You must use req.body.drain to handle each chunk as it is sent to your route.

Case Insensitive Routing

Default behavior for routing is both case-sensitive and case-preserving. Constant path components can alternately be handled in a case-insensitive and case-preserving manner for the purposes of routing; to enable this behavior, configure prior to application startup:

app.routes.caseInsensitive = true

No changes are made to the originating request; route handlers will receive the request path components without modification.

Viewing Routes

You can access your application's routes by making the Routes service or using app.routes.

print(app.routes.all) // [Route]

Vapor also ships with a routes command that prints all available routes in an ASCII formatted table.

$ swift run App routes
+--------+----------------+
| GET    | /              |
+--------+----------------+
| GET    | /hello         |
+--------+----------------+
| GET    | /todos         |
+--------+----------------+
| POST   | /todos         |
+--------+----------------+
| DELETE | /todos/:todoID |
+--------+----------------+

Metadata

All route registration methods return the created Route. This allows you to add metadata to the route's userInfo dictionary. There are some default methods available, like adding a description.

app.get("hello", ":name") { req in
    ...
}.description("says hello")

Route Groups

Route grouping allows you to create a set of routes with a path prefix or specific middleware. Grouping supports a builder and closure based syntax.

All grouping methods return a RouteBuilder meaning you can infinitely mix, match, and nest your groups with other route building methods.

Path Prefix

Path prefixing route groups allow you to prepend one or more path components to a group of routes.

let users = app.grouped("users")
// GET /users
users.get { req in
    ...
}
// POST /users
users.post { req in
    ...
}
// GET /users/:id
users.get(":id") { req in
    let id = req.parameters.get("id")!
    ...
}

Any path component you can pass into methods like get or post can be passed into grouped. There is an alternative, closure-based syntax as well.

app.group("users") { users in
    // GET /users
    users.get { req in
        ...
    }
    // POST /users
    users.post { req in
        ...
    }
    // GET /users/:id
    users.get(":id") { req in
        let id = req.parameters.get("id")!
        ...
    }
}

Nesting path prefixing route groups allows you to concisely define CRUD APIs.

app.group("users") { users in
    // GET /users
    users.get { ... }
    // POST /users
    users.post { ... }

    users.group(":id") { user in
        // GET /users/:id
        user.get { ... }
        // PATCH /users/:id
        user.patch { ... }
        // PUT /users/:id
        user.put { ... }
    }
}

Middleware

In addition to prefixing path components, you can also add middleware to route groups.

app.get("fast-thing") { req in
    ...
}
app.group(RateLimitMiddleware(requestsPerMinute: 5)) { rateLimited in
    rateLimited.get("slow-thing") { req in
        ...
    }
}

This is especially useful for protecting subsets of your routes with different authentication middleware.

app.post("login") { ... }
let auth = app.grouped(AuthMiddleware())
auth.get("dashboard") { ... }
auth.get("logout") { ... }

Redirections

Redirects are useful in a number of scenarios, such as forwarding old locations to new ones for SEO, redirecting an unauthenticated user to the login page or maintain backwards compatibility with the new version of your API.

To redirect a request, use:

req.redirect(to: "/some/new/path")

You can also specify the type of redirect, for example to redirect a page permanently (so that your SEO is updated correctly) use:

req.redirect(to: "/some/new/path", redirectType: .permanent)

The different Redirects are:

  • .permanent - returns a 301 Permanent redirect
  • .normal - returns a 303 see other redirect. This is the default by Vapor and tells the client to follow the redirect with a GET request.
  • .temporary - returns a 307 Temporary redirect. This tells the client to preserve the HTTP method used in the request.

To choose the proper redirection status code check out the full list