# Named Routing Parameters

**URL:** <https://forums.swift.org/t/named-routing-parameters/19434>\
**Category:** Vapor\
**Tags:** vapor, feedback, vapor-4\
**Created:** [January 11, 2019, 10:27pm UTC](https://forums.swift.org/t/named-routing-parameters/19434 "2019-01-11T22:27:35Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![tanner0101](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tanner0101/32/675_2.png) [@tanner0101](https://forums.swift.org/u/tanner0101)\
**Post date:** [January 11, 2019, 10:27pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/1 "2019-01-11T22:27:35Z")

</div>

# Named Routing Parameters

This is a request for feedback on an intended change to how routing works in Vapor. If feedback is positive, this change will likely land in Vapor 4.0.

## Summary

Currently, in Vapor 3, route parameters are accessed by calling `req.parameters.next()`. Each call to `next()` removes and returns a single route parameter from the internal storage. Parameters are accessed in the order that they appear in the URL string.

```swift
/posts/42/comments/1337

r.get("posts", Int.parameter, "comments", Int.parameter) { req in
    let postID = try req.parameters.next(Int.self)
    let commentID = try req.parameters.next(Int.self)
    return "Post #\(postID) Comment #(commentID)"
}

```

## Current Problem

Because parameters can only be accessed in the order they appear and parameter storage is mutated upon access, it is difficult to access arbitrary parameters without fetching all of them. It's also difficult to access parameters from different layers of an application, such as from both middleware and route closures. In these situations, parameter access must be carefully ordered to avoid unintended side effects.

The current API also does not make clear that registering multiple dynamic parameter types at a single point is not supported. For example, it is a common error to see routes registered like:

```swift
r.get("users", Int.parameter) { ... }
r.get("users", Double.parameter) { ... }

```

While one might expect both of these routes to be available, in reality only the `Double` route will be accessible since it was registered last.

## Proposed Solution

In order to simplify parameter access, a system for naming route parameters is proposed. Each dynamic parameter in a given path will be given a unique name. The unique name can be used to later fetch the parameter from the request without mutating storage.

Here is an example of the proposed API:

```swift
/posts/42/comments/1337

r.get("posts", .postID, "comments", .commentID) { req in
    let commentID = try req.parameters.get(.commentID)
    let postID = try req.parameters.get(.postID)
    return "Post #\(postID) Comment #(commentID)"
}

extension PathComponent {
    static var postID: PathComponent {
        return .dynamic(name: "postID")
    }

    static var commentID: PathComponent {
        return .dynamic(name: "commentID")
    }
}

```

Note that the parameters can be accessed in any order.

By default, `req.parameters.get(_:)` will return a `String`. If a different type is desired, the `get(_:as:)` method can be used instead:

```swift
try req.parameters.get(.postID, as: Int.self)

```

The `get(_:as:)` method will also include a default value, allowing for compiler inference.

```swift
return try Post.find(req.parameters.get(.postID), on: self.db).first()

```

## Stringly typed API

In addition to the strongly typed `static` extension to `PathComponent`, it could be possible to register dynamic path components by `String` using the prefix `:`.

```swift
/posts/42/comments/1337

r.get("posts", ":postID", "comments", ":commentID") { req in
    let postID = try req.parameters.get(":postID")
    let commentID = try req.parameters.get(":commentID")
    return "Post #\(postID) Comment #(commentID)"
}

```

While this API is slightly more prone to typos since the unique name must be written twice, it is less verbose than needing to add extensions. Inclusion of this stringly-typed addition to the API is dependent on feedback.

## Detailed Design

`PathComponent` will be updated to the following:

```swift
enum PathComponent {
    case part(String)
    case dynamic(name: String)
    case anything
    case catchall
}

```

All router methods (i.e., `r.get(...)`, `r.post(...)`, etc.) will accept `PathComponent...`.

`ExpressibleByStringLiteral` conformance will allow for paths to be registered concisely as strings.

```swift
extension PathComponent: ExpressibleByStringLiteral {
    init(stringLiteral value: String) {
        if value.hasPrefix(":") {
            self = .dynamic(name: value[1...])
        } else if value == ":" { 
            self = .anything
        } else if value == "*" { 
            self = .catchall
        } else {
            self = .part(value)
        }
    }
}

```

For example:

```swift
// normal route
r.get("users", "me") { ... }

// any parameter (discarded parameter)
r.get("users", ":", "posts")

// catchall
r.get("users", "*") { ... }

```

## Deprecation of `.next()`

`req.parameters.next()` and `Foo.parameter` will continue to work after this change. However, I propose that the API should be deprecated in order to reduce confusion over existence of two separate methods for accessing parameters.

---

<div class="post-metadata">

**Author:** ![Mordil](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mordil/32/5840_2.png) [@Mordil](https://forums.swift.org/u/Mordil)\
**Post date:** [January 12, 2019, 6:32am UTC](https://forums.swift.org/t/named-routing-parameters/19434/2 "2019-01-12T06:32:00Z")

</div>

> [@tanner0101](#):
>
> Deprecation of `.next()`

+1

> [@tanner0101](#):
>
> Stringly typed API

-1. One of the big factors that lead me to choose Vapor (Swift in general) was the type safety introduced to establishing routes. This erases the expected value type when receiving it in the route handler at the configuration point and makes me search for the route handler to see how the parameter is being used.

My biggest fear is that, because this is the "easiest" to work with, learning materials and day-to-day implementations are going to favor this over using a property reference and we've lost a key point of using a static-typed language.

> [@tanner0101](#):
>
> Proposed Solution

I think this is a great starting point for a discussion - but I think we can do better, either by passing a tuple or taking inspiration from `Codable` where a Router can be configured with say something like `CodingKeys` that provides the parameter and name.

```swift
protocol NamedParameter: CodingKey {
    var value: Parameter
}

enum MyRouteParameters: NamedParameter {
    case postID, commentID

    var parameter: Parameter {
        // switch on cases, eg. case .commentID: return Int.parameter
    }
}

let pr = r.withParameters(keyedBy: MyRouteParameters.self)
pr.get("posts", .postID, "comments", .commentID) { req in
    let commentID = try req.parameters.get(.postID)
    // etc.
}

```

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [January 12, 2019, 11:20am UTC](https://forums.swift.org/t/named-routing-parameters/19434/3 "2019-01-12T11:20:02Z")

</div>

> [@tanner0101](#):
>
> Stringly typed API

Hello,

I'm not a Vapor user, so please take my answer with a grain of salt.

I expect you'll get some pushback on the string-based API because many readers of those forums favor type-safety above all.

It may well be useful to explain the purpose of the string-based API. The reason why it is there. I'm not sure, but I guess that those strings are not only a leak of the underlying implementation that should be hidden. I also expect those strings to allow the design of dynamic routes (ones that are not tied to statically defined constants). But you know better.

---

<div class="post-metadata">

**Author:** ![JhonnyBillM](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jhonnybillm/32/15739_2.png) [@JhonnyBillM](https://forums.swift.org/u/JhonnyBillM)\
**Post date:** [January 12, 2019, 1:23pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/4 "2019-01-12T13:23:22Z")

</div>

## Deprecation of .next()

**+1**

## Stringly typed API

**-1**  
I think this will be too error prone + we will lose the type safety. I think the best is to enforce the use of a type safety approach [as in 'general' in Swift].

## Proposed Solution

**I like it, good start,** - it's just I don't like to do:

```swift
 r.get("posts", .postID, "comments", .commentID)

```

And I think is kind of confusing.

**Instead I would like for it to be:**

```swift
 r.get(.postID, .commentID)

```

#### **How?**

_- I'm sorry, I am too noob, but I'll write what I think -_

We can make a protocol `[ParameterProtocol]`, and then ask users to make an Enum conforming to that protocol so .get has a variadic `ParameterProtocol` argument.  
E.g

ParameterProtocol

```swift
protocol ParameterProtocol {
    var routeParameter: String { get }
    var pathComponent: PathComponent { get }
}

```

Then, we can define an Enum like:

```swift
enum MyPathComponents: ParameterProtocol {
    case postID
    case commentID
    
    var routeParameter: String {
        switch self {
        case .postID:
            return "posts"
        case .commentID:
            return "comments"
        }
    }
    
    var pathComponent: PathComponent {
        switch self {
        case .postID:
            return .dynamic(name: "postID")
        case .commentID:
            return .dynamic(name: "commentID")
        }
    }
}

```

with this we can make the call:

```swift
r.get(MyPathComponents.postID, MyPathComponents.commentID)

```

which I think is cleaner, Swifty and safer.

The ultimate goal would be to get this call as

```swift
r.get(.postID, .commentID)

```

What I am not so sure about this approach is what if it is too much? or how easily will this be to newcomers ?

---

<div class="post-metadata">

**Author:** ![0xTim](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/0xtim/32/27968_2.png) [@0xTim](https://forums.swift.org/u/0xTim)\
**Post date:** [January 12, 2019, 3:52pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/5 "2019-01-12T15:52:52Z")

</div>

I mostly agree with the above - though having an optional stringly-typed method available to be more flexible isn't a bad idea, as long as the priority was given to those who require the flexibility.

> [@JhonnyBillM](#):
>
> ```swift
> r.get("posts", .postID, "comments", .commentID)
> 
> ```
> 
> And I think is kind of confusing.
> 
> **Instead I would like for it to be:**
> 
> ```swift
> r.get(.postID, .commentID)
> 
> ```

IMO this is a bad idea - how do you then differentiate between `/posts/<POSTID>/comments/<COMMENTID>` and `/<SOME_ID>/<ANOTHER_ID>`. Additionally if the IDs were `String`s (which Fluent supports) then you could cause route conflicts.

One thing that would be nice to see would be compiler warnings if routes were registered over the top of existing routes - not sure how feasible this is though

---

<div class="post-metadata">

**Author:** ![Scott](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/scott/32/23832_2.png) [@Scott](https://forums.swift.org/u/Scott)\
**Post date:** [January 12, 2019, 10:36pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/6 "2019-01-12T22:36:28Z")

</div>

> [@tanner0101](#):
>
> Deprecation of `.next()`

+1

> [@tanner0101](#):
>
> Stringly typed API

I'm not a huge fan of this being here along with the other method. I think if the proposed solution is too much boilerplate, the proposed solution probably isn't sufficient. I don't have a problem with the stringly typed API itself, but I think there should probably be one way to do it unless one of them offers different abilities.

---

<div class="post-metadata">

**Author:** ![twof](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/twof/32/1628_2.png) [@twof](https://forums.swift.org/u/twof)\
**Post date:** [January 12, 2019, 11:25pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/7 "2019-01-12T23:25:14Z")

</div>

I pretty much agree with everyone's rejection of Stringly typed APIs.

Have any tests been run to see what sort of performance hit you'd take if you typecasted parameters when doing route matching? I think it would be doable as long as the parameter type conforms to `LosslessDataConvertable`

Edit: Got a PR for typecasting parameters, but this doesn't actually solve the problem at hand. Ignore me.

> <https://github.com/vapor/routing-kit/pull/52>
>
> Notes:
> 
> This is really quick and dirty. I'll leave notes in places that likely… need cleanup, but users can now provide the type they expect to receive in parameters, and route matching will fail if a parameter can't be casted to the expected type (sorta).
> 
> I realized part of the way through that we don't need to actually go through the work of converting from Data to another type, we just need to run checks on the data to ensure conversion is possible. I don't know if that would be easier or less intensive, but maybe some heuristics could be created to do the necessary checks?

---

<div class="post-metadata">

**Author:** ![Francois\_Green](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/francois_green/32/2998_2.png) [@Francois\_Green](https://forums.swift.org/u/Francois_Green)\
**Post date:** [January 13, 2019, 7:58am UTC](https://forums.swift.org/t/named-routing-parameters/19434/8 "2019-01-13T07:58:22Z")

</div>

I like having a choice of APIs: Not everyone coming to Vapor will be steeped in Swift lore, nor will they care learn immediately. Further, for devs porting existing applications from other platforms, where "stringly" typed APIs might be common, the familiarity might allow them to get up and running faster. After all, no one is being forced to use it; it's an option.

---

<div class="post-metadata">

**Author:** ![kdawgwilk](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/kdawgwilk/32/479_2.png) [@kdawgwilk](https://forums.swift.org/u/kdawgwilk)\
**Post date:** [January 14, 2019, 5:27am UTC](https://forums.swift.org/t/named-routing-parameters/19434/9 "2019-01-14T05:27:30Z")

</div>

Could any of the new String Interpolation features be used here? If I remember correctly they add some great type safety that didn’t exist previously. Seemed like there was already some discussions to improve logging with Swift on the server and might be useful here as well?

---

<div class="post-metadata">

**Author:** ![tanner0101](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tanner0101/32/675_2.png) [@tanner0101](https://forums.swift.org/u/tanner0101)\
**Post date:** [January 17, 2019, 5:43pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/10 "2019-01-17T17:43:28Z")

</div>

> [@0xTim](#):
>
> One thing that would be nice to see would be compiler warnings if routes were registered over the top of existing routes - not sure how feasible this is though

We can definitely do runtime warnings, but I can't think of anyway to push it to compile time.

> [@twof](#):
>
> Have any tests been run to see what sort of performance hit you'd take if you typecasted parameters when doing route matching?

I haven't, but it would definitely be slower. Though my main concern here is less about speed and more about having a simpler, more familiar model.

> [@Francois\_Green](#):
>
> Further, for devs porting existing applications from other platforms, where "stringly" typed APIs might be common, the familiarity might allow them to get up and running faster. After all, no one is being forced to use it; it's an option.

I agree 100%. This was the main reasoning behind adding it.

* * *

Furthering what I said in the initial post, I think having a simpler model here for how routing works would actually help us build _better_ APIs going forward. For example, imagine something like this:

```swift
/posts/42/comments/1337

r.get("posts", \Post.id.parameter, "comments", \Comment.id.parameter) { req in
    let commentID = try req.parameters.get(\Comment.id)
    let postID = try req.parameters.get(\Post.id)
    return "Post #\(postID) Comment #(commentID)"
}

```

This would be possible with a couple simple extensions:

```swift
extension KeyPath {
    static var parameter: PathComponent {
        return .dynamic(name: "\(Root.self)_\(Value.self)")
    }
}

extension Parameters {
    func get<R, V>(_ keyPath: KeyPath<R, V>) throws -> V {
        return self.get(keyPath.parameter, as: V.self)
    }
}

```

---

<div class="post-metadata">

**Author:** ![eneko](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/eneko/32/12117_2.png) [@eneko](https://forums.swift.org/u/eneko)\
**Post date:** [January 19, 2019, 5:09am UTC](https://forums.swift.org/t/named-routing-parameters/19434/11 "2019-01-19T05:09:59Z")

</div>

Would it be possible to do this third option in addition to the two above?

```swift
/posts/42/comments/1337

r.get("posts/:postID/comments/:commentID") { req in
    let commentID = try req.parameters.commentID
    let postID = try req.parameters.postID
    return "Post #\(postID) Comment #(commentID)"
}

```

Yes, yes, strings! I know, they lead to typos.

This, however, could make it super easy for people coming from other platforms. The idea is to parse the string url to extract the two parameters, then use Swift 4 dynamic properties to access them (as Strings)

Thoughts?

---

<div class="post-metadata">

**Author:** ![Scott](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/scott/32/23832_2.png) [@Scott](https://forums.swift.org/u/Scott)\
**Post date:** [January 21, 2019, 9:09pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/12 "2019-01-21T21:09:44Z")

</div>

Allowing the route to be written like

```
r.get("posts/:postID/comments/:commentID") { req in

```

makes more sense to me than letting you use `":postID"` in the comma separated list, as this is commonly how you write routes in other frameworks.

Still not sure how I feel about dynamic properties

---

<div class="post-metadata">

**Author:** ![Mordil](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mordil/32/5840_2.png) [@Mordil](https://forums.swift.org/u/Mordil)\
**Post date:** [January 21, 2019, 10:01pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/13 "2019-01-21T22:01:59Z")

</div>

I don't see how they would be mutually exclusive.

---

<div class="post-metadata">

**Author:** ![Scott](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/scott/32/23832_2.png) [@Scott](https://forums.swift.org/u/Scott)\
**Post date:** [January 21, 2019, 10:31pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/14 "2019-01-21T22:31:28Z")

</div>

they aren't, you're right.

---

<div class="post-metadata">

**Author:** ![eneko](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/eneko/32/12117_2.png) [@eneko](https://forums.swift.org/u/eneko)\
**Post date:** [January 21, 2019, 10:45pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/15 "2019-01-21T22:45:08Z")

</div>

> [@Scott](#):
>
> Still not sure how I feel about dynamic properties

Swift 4.2 dynamic properties are a bit scary, at first. But in reality, is not much different than retrieving a value from a dictionary, which needs to check at run-time if a value is stored under a given key.

This is, both `req.parameters["postID"]` and `req.parameters.postID` are resolved at run-time.

This is an excellent use case for dynamic properties in Swift.

---

<div class="post-metadata">

**Author:** ![tanner0101](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tanner0101/32/675_2.png) [@tanner0101](https://forums.swift.org/u/tanner0101)\
**Post date:** [January 23, 2019, 3:50pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/16 "2019-01-23T15:50:46Z")

</div>

> [@eneko](#):
>
> Would it be possible to do this third option in addition to the two above?

```swift
r.get("posts/:postID/comments/:commentID")

```

This part, yes. An additional case could be added to `PathComponent` to support an array of itself. i.e., `PathComponent.array([PathComponent])`.

```swift
let commentID = try req.parameters.commentID

```

This would not be possible since computed properties / subscripts cannot throw. The most we could do here is return an optional.

However, this would still work:

```swift
let commentID = try req.parameters.get(":commentID")

```

---

<div class="post-metadata">

**Author:** ![eneko](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/eneko/32/12117_2.png) [@eneko](https://forums.swift.org/u/eneko)\
**Post date:** [January 23, 2019, 6:19pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/17 "2019-01-23T18:19:00Z")

</div>

Nice to hear about the path string, I feel it would be great and preferred by many developers.

For parameter retrieval, could we have all these?

```swift
let commentID = req.parameters.commentID // Optional
let commentID = try req.parameters.get(":commentID") // Non-optional
let commentID = try req.parameters.get(.commentID) // Non-optional

```

Using `guard` + `throw` for the optional one would be a bit redundant, though, as the assumption is this request handler would never get called if any of those two parameters were missing.

---

<div class="post-metadata">

**Author:** ![tanner0101](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tanner0101/32/675_2.png) [@tanner0101](https://forums.swift.org/u/tanner0101)\
**Post date:** [January 23, 2019, 8:40pm UTC](https://forums.swift.org/t/named-routing-parameters/19434/18 "2019-01-23T20:40:22Z")

</div>

> [@eneko](#):
>
> For parameter retrieval, could we have all these?

Previously, we've used a combination of `get` and `subscript` for throwing and optional methods, respectively. One advantage to using subscripts that is we can have more than one argument. For example:

```swift
try req.parameters.get(":commentID", as: Int.self) // Int
req.parameters[":commentID", as: Int.self] // Int?

```

Dynamic property access could only result in a `String`. Although, I'm not sure that's necessarily a bad thing. It's pretty easy to map the `String` to another type, albeit more verbose.

```swift
guard let commentID = req.parameters.commentID.flatMap(Int.init) else {
    throw ...
}

```

That said, I think it might be confusing to have both a subscript _and_ dynamic property API at the same time. Given the subscript API is more flexible, I would lean toward that.

---

<div class="post-metadata">

**Author:** ![twof](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/twof/32/1628_2.png) [@twof](https://forums.swift.org/u/twof)\
**Post date:** [January 24, 2019, 7:19am UTC](https://forums.swift.org/t/named-routing-parameters/19434/19 "2019-01-24T07:19:24Z")

</div>

This is kind of an oddball, but what about something like this?

```swift
func get<T1, T2>(url: String, _ paramType1: T1.Type, _ paramType2.Type callback: (Request, T1, T2) -> Response)

r.get("posts/:postID/comments/:commentID", params: Post.ID.self, Comment.ID.self) { req, postID /*: Post.ID*/, commentID/*: Comment.ID*/ in
   ...
}

```

We could code gen up to ~6 parameters to capture the majority of use cases.

Edit: There's nothing stopping a mismatch between the number of dynamic params in the path and the number of parameter types, so that's something to either solve or mitigate with this one.

---

<div class="post-metadata">

**Author:** ![0xTim](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/0xtim/32/27968_2.png) [@0xTim](https://forums.swift.org/u/0xTim)\
**Post date:** [January 24, 2019, 8:37am UTC](https://forums.swift.org/t/named-routing-parameters/19434/20 "2019-01-24T08:37:44Z")

</div>

That’s like the routing from Vapor 1 I believe (@tanner0101 can correct me), which worked up to 3 parameters. Any more than that and you’d end up with compile times of around an hour....

[Next page](https://forums.swift.org/t/named-routing-parameters/19434.md?page=2)
