How to Make REST API on Swift Even Better. Vapor RestKit 2.0 is Out
I've finally pushed it out as promised.
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!
Comments