2 min read

How To: REST API in Swift with Vapor RestKit — ORM Extensions

One of the things I worked on some time ago was Vapor Rest Kit It started as a set of utils I implemented to speed up the development of my side project. Gradually my Vapor utils grew rather large and decided to move it to a separate package and publish.
How To: REST API in Swift with Vapor RestKit — ORM Extensions
Photo by Leohoho / Unsplash

One of the things I worked on some time ago was Vapor Rest Kit

It started as a set of utils I implemented to speed up the development of my side project.

Gradually my Vapor utils grew rather large and decided to move it to a separate package and publish.

Features

Currently, the feature set has grown so large that it's probably worth breaking down into separate smaller libs🤔  

One of the very first features was Fluent Model Extensions allowing to find answers for the following questions.

How to stop suffering from string literals in Fluent Models' Fields

When I first started using Vapor 4 in spring 2020, I was surprised that Fluent Models now require all those handwritten fields keys everywhere: when defining model fields, when adding migrations, etc.

Vapor docs examples were full of string literals that were duplicated everywhere.

That seemed rather error prone and unnatural, so I added a simple helper to define model fields as enum and avoid errors caused by fat fingers.

FieldsProvidingModel

extension User: FieldsProvidingModel {
    enum Fields: String, FieldKeyRepresentable {
        case username
    }
}

final class User: Model, Content {
    static let schema = "users"

    @ID(key: .id)
    var id: UUID?

    @Field(key: Fields.username.key)
    var username: String?
    
}
    
 extension User: InitMigratableSchema {
    static func prepare(on schemaBuilder: SchemaBuilder) -> EventLoopFuture<Void> {
        return schemaBuilder
            .id()
            .field(Fields.username.key, .string)
            .create()
    }
}


How to stop suffering from Fluent Models initial migrations boilerplate

Every model in Vapor ORM Fluent requires initial migration that also include some additional boilerplate code. I've added some sugar just to make it faster.

InitMigratableSchema

Easy-breazy stuff for creating initial migrations in three simple steps:

Make your model conform to InitMigratableSchema protocol, by implementing static prepare(...) method (almost as the same as in Vapor Docs)

extension User: InitMigratableSchema {
    static func prepare(on schemaBuilder: SchemaBuilder) -> EventLoopFuture<Void> {
        return schemaBuilder
            .id()
            .field(Fields.username.key, .string)
            .create()
    }
}

Add initial migration for the model in your configuration file:

func configureDatabaseInitialMigrations(_ app: Application) {
    app.migrations.add(User.createInitialMigration()) 
}

That's it!

How to stop suffering from Siblings pivot models boilerplate

Creating siblings relations in Vapor requires extra work. We have to define a model for pivot table and migration for it which is not very convenient.

SiblingRepresentable

I've implemented a helper to turn it into almost one-liner.

Define whatever as SiblingRepresentable (enum for example)

extension Todo {
    enum Relations {
        enum RelatedTags: SiblingRepresentable {
            typealias From = Todo
            typealias To = Tag
            typealias Through = SiblingModel<Todo, Tag, Self>
        }
    }
}

Add corresponding property with @Siblings property wrapper

final class User: Model, Content {
    static let schema = "users"

    @ID(key: .id)
    var id: UUID?

    @Siblings(through: Relations.RelatedTags.through, from: \.$from, to: \.$to)
    var relatedTags: [Tag]
}

Add initial migration for pivot Fluent model representing the sibling right after related models initial migrations:

private func configureDatabaseInitialMigrations(_ app: Application) {
    app.migrations.add(Todo.createInitialMigration())
    app.migrations.add(Tag.createInitialMigration()) 
    app.migrations.add(Todo.Relations.RelatedTags.Through.createInitialMigration())
}

Profit! No need to feel pain from pivot models manual creation any more.

Github:

Vapor RestKit