I've finally pushed it out.

I've persisted backward compatibility with 1.x version and only marked old APIs as deprecated. (yeah, this will produce a ton of warnings)

The internals and tests are a little bit messy now as they actually contain both old and new implementations at the same time.

Eventually, I will remove the old stuff. I decided not to do it now so as not to break projects, relying on it.

BTW, I've migrated one of my personal projects from 1.x to 2.0 and it took like half of the day or even less.

2.0 is a big step towards simplicity. I've removed odd things like forced versioning and magical declarative API in favor of plain old good Vapor routes declarations and now Vapor RestKit feels much more minimal and simple.

This also opens a door to further improvements like async/await API.

Features

I'm pretty much satisfied with the scope of things it handles.

  • Explicit Inputs and Outputs for RestAPI consistency
  • Sort, Filter, and EagerLoad queries handling
  • Cursor Pagination
  • Model relations handling for nested query paths

Things that are almost always needed for every second Rest API and always take time to get done properly.

RestKit 2.0 Example:

1. Implement Inputs, Outputs

 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. Implement Controller

struct TodoController {
    func create(req: Request) throws -> EventLoopFuture<Todo.Output> {
        try ResourceController<Todo.Output>().create(req: req, using: Todo.Input.self)
    }

    func read(req: Request) throws -> EventLoopFuture<Todo.Output> {
        try ResourceController<Todo.Output>().read(req: req)
    }

    func update(req: Request) throws -> EventLoopFuture<Todo.Output> {
        try ResourceController<Todo.Output>().update(req: req, using: Todo.Input.self)
    }

    func patch(req: Request) throws -> EventLoopFuture<Todo.Output> {
        try ResourceController<Todo.Output>().patch(req: req, using: Todo.PatchInput.self)
    }

    func delete(req: Request) throws -> EventLoopFuture<Todo.Output> {
        try ResourceController<Todo.Output>().delete(req: req)
    }

    func index(req: Request) throws -> EventLoopFuture<CursorPage<Todo.Output>> {
        try ResourceController<Todo.Output>().getCursorPage(req: req)
    }
}

3. Routes setup

app.group("todos") {
    let controller = TodoController()

    $0.on(.POST, use: controller.create)
    $0.on(.GET, Todo.idPath, use: controller.read)
    $0.on(.PUT, Todo.idPath, use: controller.update)
    $0.on(.DELETE, Todo.idPath, use: controller.delete)
    $0.on(.PATCH, Todo.idPath, use: controller.patch)
    $0.on(.GET, use: controller.index)
}

4. Profit!

Github

Place to put stars