Providing URIs for newly created resources

Hadi Hariri

Often times, in response to an HTTP POST request, we need to provide an ID of a newly created entity.

For instance when creating a new record in a database, much like we can have the storage
return us the ID, we would like to then provide the same information in the response.

There are multiple ways of doing this, but the most common one used is to provide this information as part of the response payload.

For instance, with the following request:

POST /customer 
Content-Type: application/json

We could respond with:

{ 
    "id": "5251",
    "name": "Mary Smith",
    "url": "https://domain.com/customer/768AF2"
}

and this would be fine if we wanted to include the URI as part of other information being sent back. However, if the only thing we want to send is the URI, then it feels like an overkill.

Location Header

That’s where the HTTP Location header comes in (not to be confused with Ktor Locations).

RFC 7231 indicates:

   For 201 (Created) responses, the Location value refers to the primary
   resource created by the request.  For 3xx (Redirection) responses,
   the Location value refers to the preferred target resource for
   automatically redirecting the request.

The first use-case is actually what we’re after, that is, providing the URI for the newly created resource.

Doing this is pretty straightforward. All we need to do return a Location header with the right URI. In Ktor, this would be as simple as:

post("/manual") {
    call.response.header("Location", "/manual/AF4GH")
    call.response.status(HttpStatusCode.Created)
    call.respondText("Manually setting the location header")
}

Defining an extension function

Given we’re working with Kotlin, we could easily create an extension function that sets the header as well as the status code in one go:

private fun ApplicationResponse.created(id: String) {
    call.response.status(HttpStatusCode.Created)
    call.response.header("Location", "${call.request.uri}/$id")
}

Using this function would simply require passing in the ID of the newly created resource:

post("/extension") {
    call.response.created("AF4GH")
    call.respondText("Extension setting the location header")
}

You may be asking whether this could be accomplished in Ktor using a Feature. While it’s certainly possible, it’s not strictly necessary and wouldn’t provide much value. Using a Feature would make sense if we would have to somehow change the order in which we set values in the response pipeline, which is not the case here.

A word on Ktor Locations

Previously, we mentioned that the HTTP Location should not be mistaken with Ktor locations. This is actually often a source of confusion
for some folks. Ktor locations allows us to use @Location annotation to define strongly-typed routes and shouldn’t be mistaken with
the HTTP Location header. This confusion is actually one reason which we’re debating whether we should rename the annotation before we finalise the feature (currently experimental).