Basics

While Vapor's Model is something represented by a table in your database, RestKit introduces such thing as ResourceModel.

ResourceModel is a wrap over the Model that is returned from backend API as a response or consumed by backend API as a request.

Actually, there are three types of ResourceModels in Vapor RestKit:

One for output:

protocol ResourceOutputModel: Content {
    associatedtype Model: Fields

    init(_: Model)
}

and two for input: Update and Patch ResourceModels:

protocol ResourceUpdateModel: Content, Validatable {
    associatedtype Model: Fields

    func update(_: Model) -> Model
}

protocol ResourcePatchModel: Content, Validatable {
    associatedtype Model: Fields

    func patch(_: Model) -> Model
}

ResourceModels provide manageable interface for the underlying database models. My implementation was initially inspired by Tibor Bödecs's post about Generic CRUD controllers and a CRUD-Kit library.

The difference is that by design, the libs above allow only single input and output per model. In my implementation each database model can have as many possible inputs and outputs. And here is why.

The problem

When we start with the first version of our backend API we usually have a single output for the model across all endpoints. And that's very convenient both for server and client side.

When our app gets more mature models may become too heavy, require joins with other tables, etc.

At some point we may start to wish some compact version of the model along with full model detailed output.

Furthermore, when the app gets even more mature, we may want to version our model outputs so we could maintain old clients along with new ones.

Vapor RestKit provides a manageable way to maintain Inputs and Outputs versioning in a type-safe manner.

CRUD for Resource Models

Writing CRUD APIs is something that gets us bored VERY fast. Here comes RestKit with a vaccine from CRUD boredom.

Resource CRUD Controllers

CRUD controller creation process is pretty easy and consists of three simple steps.

  1. Create Inputs, Outputs for your model:
extension Todo {
    struct Output: ResourceOutputModel {
        let id: Int?
        let title: String

        init(_ model: Todo, req: Request) {
            id = model.id
            title = model.title
        }
    }
    
    struct CreateInput: ResourceUpdateModel {
        let title: String

        func update(_ model: Todo) throws -> Todo {
            model.title = title
            return model
        }

        static func validations(_ validations: inout Validations) {
            //Validate something
        }
    }
    
    struct UpdateInput: ResourceUpdateModel {
        let title: String

        func update(_ model: Todo) throws -> Todo {
            model.title = title
            return model
        }

        static func validations(_ validations: inout Validations) {
            //Validate something
        }
    }

    struct PatchInput: ResourcePatchModel {
        let title: String?

        func patch(_ model: Todo) throws -> Todo {
            model.title = title ?? model.title
            return model
        }

        static func validations(_ validations: inout Validations) {
            //Validate something
        }
    }

}

2. Using resource controller builder, we can define which operations will be supported by resource controller:

let controller = Todo.Output
                    .controller(eagerLoading: EagerLoadingUnsupported.self)
                    .create(using: Todo.CreateInput.self)
                    .read()
                    .update(using: Todo.UpdateInput.self)
                    .patch(using: Todo.PatchInput.self)
                    .delete()
                    .collection(sorting: DefaultSorting.self,
                                filtering: DefaultFiltering.self)

3. Add controller's methods to Vapor's routes builder:

controller.addMethodsTo(routeBuilder, on: "todos")

That's it! It will add the following methods to your API endpoint:

HTTP Method Route Result
POST /todos Create new
GET /todos/:todoId Show existing
PUT /todos/:todoId Update existing (Replace)
PATCH /todos/:todoId Patch exsiting (Partial update)
DELETE /todos/:todoId Delete
GET /todos Show list

Customize Delete method Output

Luckily RestKit allows to take part in endless disputes about correct response format for DELETE methods and specify any output.

How to change Delete method output

By default, if delete controller is  defined the following way and will return the deleted model as response:

let controller = Todo.Output
                    .controller(eagerLoading: EagerLoadingUnsupported.self)
                    .delete()

It's possible to use different output for Delete method, or use default SuccessOutput:

let controller =  SuccessOutput<Todo>
                    .controller(eagerLoading: EagerLoadingUnsupported.self)
                    .delete()

Github:

Vapor RestKit