|
1 | 1 | # Vapor Route Builder |
2 | 2 |
|
3 | | -Build Vapor routes using a declarative syntax langauge similar to how you would write SwiftUI views. |
| 3 | +Build and define Vapor routes using a declarative syntax langauge similar to how you would write SwiftUI views. |
| 4 | + |
| 5 | +[Learn more](https://github.com/vapor/vapor) about 💧 Vapor, an HTTP web framework for Swift. |
| 6 | + |
| 7 | +## Usage |
| 8 | + |
| 9 | +> [!IMPORTANT] |
| 10 | +> Before learning about `@RouteBuilder`, make sure you've [learned the basics](https://docs.vapor.codes/basics/routing/) of routing with Vapor. |
| 11 | +
|
| 12 | +Defining routes using `@RouteBuilder` is fairly straightforward. You simply call `.register(_:)` on your `Application`, providing the routes that you wish to support. |
| 13 | + |
| 14 | +```swift |
| 15 | +app.register { |
| 16 | + GET("latest") { ... } |
| 17 | + GET("popular") { ... } |
| 18 | + POST("favorite") { ... } |
| 19 | +} |
| 20 | +``` |
| 21 | + |
| 22 | +### Path Groups |
| 23 | + |
| 24 | +You can use `Group` to help organize your routes and cut down on the repetitiveness of defining long paths. |
| 25 | + |
| 26 | +```swift |
| 27 | +app.register { |
| 28 | + Group(path: "api", "v1") { |
| 29 | + Group(path: "movies") { |
| 30 | + GET("latest") { ... } |
| 31 | + GET("popular") { ... } |
| 32 | + GET(":movie") { ... } |
| 33 | + } |
| 34 | + Group(path: "books") { |
| 35 | + GET("new") { ... } |
| 36 | + GET("trending") { ... } |
| 37 | + GET(":book") { ... } |
| 38 | + } |
| 39 | + } |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +### Middleware Groups |
| 44 | + |
| 45 | +Sometimes you may want to wrap certain routes in middleware. A common use case requiring middleware could be authentication. You have have some routes that require middleware, and others that do now. Adding middleware using `@RouteBuilder` is similar to wrapping routes in `Group(path:)`. |
| 46 | + |
| 47 | +```swift |
| 48 | +app.register { |
| 49 | + Group(path: "api", "v1") { |
| 50 | + Group(middleware: AuthenticationMiddleware()) { |
| 51 | + Group(path: "profile") { |
| 52 | + GET("favorites") { ... } |
| 53 | + GET("friends") { ... } |
| 54 | + } |
| 55 | + } |
| 56 | + Group(path: "books") { |
| 57 | + GET("new") { ... } |
| 58 | + GET("trending") { ... } |
| 59 | + GET(":book") { ... } |
| 60 | + } |
| 61 | + } |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +In the above example, you'll see that we've only wrapped our `/profile/*` endpoints in our `AuthenticationMiddleware`, while all of the `/book/*` endpoints have no middleware associated with them. |
| 66 | + |
| 67 | +If you have more than one middleware... no worries, `Group(middleware:)` accepts a variadic amount of middleware. |
| 68 | + |
| 69 | +```swift |
| 70 | +Group(middleware: Logging()) { |
| 71 | + GET("foo") { ... } |
| 72 | + Group(middleware: Authentication(), Validator()) { |
| 73 | + GET("bar") { ... } |
| 74 | + } |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +Remember that order matters here. Incoming requests will always execute middleware from top to bottom. So in the above example, the order of an incoming request would be as follows ➡️ `Logging`, `Authentication`, `Validator`. Outgoing respones will always execute middleware in the reverse order. ➡️ `Validator`, `Authentication`, `Logging`. |
| 79 | + |
| 80 | +### Making Custom Route Components |
| 81 | + |
| 82 | +Often times, as your routes grow, a single large definition can become unwieldly and cumbersome to read and update. Organization of routes can be straightforward with `@RouteBuilder` |
| 83 | + |
| 84 | +```swift |
| 85 | +struct MoviesUserCase: RouteComponent { |
| 86 | + var body: some RouteComponent { |
| 87 | + Group(path: "movies") { |
| 88 | + MovieUseCase() |
| 89 | + GET("latest") { ... } |
| 90 | + GET("trending") { ... } |
| 91 | + } |
| 92 | + } |
| 93 | +} |
| 94 | + |
| 95 | +struct MovieUseCase: RouteComponent { |
| 96 | + var body: some RouteComponent { |
| 97 | + Group(path: ":movie") { |
| 98 | + GET("credits") { ... } |
| 99 | + GET("related") { ... } |
| 100 | + } |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +struct BooksUseCase: RouteComponent { |
| 105 | + var body: some RouteComponent { |
| 106 | + ... |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +app.register { |
| 111 | + MoviesUseCase() |
| 112 | + BooksUseCase() |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +### Convenience Routes |
| 117 | + |
| 118 | +This package ships with a few conveniences for creating routes. You can use `GET`, `POST`, `PUT`, `PATCH`, and `DELETE` to cut down on the verbosity of defining your routes. If you need more fine grained control, you can always fall back to using a `Route` directly in your `@RouteBuilder` |
| 119 | + |
| 120 | +```swift |
| 121 | +Group(path: "movies", ":movie") { |
| 122 | + Route(.OPTIONS, "credits") { ... } |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +### Expressions & Logic |
| 127 | + |
| 128 | +`@ResultBuilder` supports a wide variety of the available result builder syntax. |
| 129 | + |
| 130 | +```swift |
| 131 | +app.register { |
| 132 | + if isStaging { |
| 133 | + GET("configuration") { ... } |
| 134 | + POST("configuration") { ... } |
| 135 | + } |
| 136 | + |
| 137 | + GET("latest") { ... } |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +```swift |
| 142 | +app.register { |
| 143 | + for category in categories { |
| 144 | + Group(path: "\(category.rawValue)") { |
| 145 | + switch category { |
| 146 | + case .movies: |
| 147 | + GET(":movie") { ... } |
| 148 | + case .books: |
| 149 | + GET(":book") { ... } |
| 150 | + } |
| 151 | + } |
| 152 | + } |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +### RouteModifiers |
| 157 | + |
| 158 | +- Currently undocumented. |
4 | 159 |
|
5 | 160 | ## TODO |
6 | 161 |
|
7 | | -- [Confirmed] Add README usage docs. |
8 | 162 | - [Confirmed] Implement Websocket support |
| 163 | +- [Confirmed] Handle routes at the root of a RouteComponent. |
| 164 | + |
| 165 | +```swift |
| 166 | +Group(path: ":movie") { |
| 167 | + ... How do we handle JUST ":movie"? |
| 168 | + GET("credits") { ... } |
| 169 | + GET("category") { ... } |
| 170 | +} |
| 171 | +``` |
| 172 | + |
9 | 173 | - [Maybe] Implement EnvironmentObject style support. |
10 | 174 | - [Maybe] Implement Service support |
11 | | - |
| 175 | +- [Maybe] Route modifier for description. |
| 176 | +- [Maybe] Route modifier for caseInsensitive. |
| 177 | +- [Maybe] Route modifier for defaultMaxBodySize. |
0 commit comments