Decoding Request Parameters

Episode #373 | 9 minutes | published on January 25, 2019 | Uses swift-4.2, Xcode-10.1, Vapor-3.0.8, fluent-postgresql-1.0.0
Subscribers Only
Taking input from a request body in order to update or create a record is extremely easy with Vapor. In this episode we will update our create and update routes for Projects and take in JSON input from the request in order to modify Projects. We also talk about decoupling the request model from our actual model to prevent updating certain internal attributes from being modified.

Specifying Request Content to Decode

We can leverage Vapor to automatically decode any Content-conforming type. To start, we can decode our Project model directly:

router.post(content: Project.self, use: create)

Our route handler also needs to change to add a new parameter:

private func create(_ req: Request, _ project: Project) throws -> Future<Project> {
    return try project.save(on: req)
}

How easy is that! You can really see how strongly-typed nature of Swift can make certain jobs really easy in Vapor.

Preventing some attributes from being modified

We probably don't want people to modify the id property, or any of the createdAt, updatedAt or any other internal field.

To specify this, we can create a simplified Codable model that we'll use as the user-facing structure:

struct ProjectContent : Content {
    var title: String?
    var description: String?

    func buildProject() -> Project {
        return Project(title: String ?? "<untitled>", description: description ?? "")
    }
}

Now we can modify the create route:

router.post(ProjectContent.self, use: create)

And the route function itself:

private func create(_ req: Request, _ projectContent: ProjectContent) throws -> Future<Project> {
    return projectContent.buildProject().save(on: req)
}

With this in place, only the title and description are available for an API client to modify. This is also a great place to put fallback values.

Updating the Project

The process here is similar. First we update our route to define what the type of content will be expected on the request:

router.put(ProjectContent.self, at: Project.parameter, use: update)

Then modify the route function:

private func update(_ req: Request, _ projectContent: ProjectContent) throws -> Future<Project> {
    return try req.parameters.next(Project.self)
        .flatMap { project in 
            project.title = projectContent.title ?? project.title
            project.description = projectContent.description ?? project.description
            return project.update(on: req)
        }
}

Here we make sure to only set the properties if they are non-nil, so that the project retains any previous values. Then we update the record in the database.

blog comments powered by Disqus