Creating and deploying a Vapor (Swift) web app into Heroku cloud platform — Part (2/2)
By: Ricardo Santos
Tags: Postgres, Swift, Swift Programming, Heroku, Vaporswift

GreetingModel

public final class GreetingModel: Model, Content {

public static let schema = "greeting"



@ID(custom: "id")

public var id: Int? @Field(key: "from")

var greetingFrom: String public init() {

self.greetingFrom = ""

} public init(greetingFrom: String = "") {

self.greetingFrom = greetingFrom

}

}

We have a struct named GreetingModel . This struct will be a wrapper around our table records.

Notice that it conforms with Content and Model .

About Content protocol compliance, will automatically add conformance to Codable alongside additional utilities for working with the content API.

About Model protocol compliance, GreetingModel will need to implement schema (our table name) and an $ID(custom:) property wrapper matching the table primary key. We also need the $Field(key:) property wrappers to bind with all the table columns. BUT……

Image 12

…now that GreetingModel conforms with Model , GreetingModel will automatically earn the implementation of database utils like save , create , update , query and others given by the Fluent framework.

This will allow us to do things like GreetingModel.query(on: app.db).all() .

Routing

Now that we have GreetingModel that can wrapper the connection with the table greetings (on our database), is time for routing.

We added two methods on routes(_ app: Application) function

greetings GET

GET greetings/add POST

app.get("greetings") { req -> EventLoopFuture<[GreetingModel]> in

let db = Bool.random() ? req.db : app.db

return GreetingModel.query(on: db).all()

} app.post("greetings", "add") { req -> EventLoopFuture<GreetingModel> in

let record = try req.content.decode(GreetingModel.self)

let db = Bool.random() ? req.db : app.db

return record.create(on: db).map { record }

}

We have let db = Bool.random() ? req.qd : app.db . This is just to show that we can acquire our database reference using the app parameter passed on routes(_ app: Application) , but also using the req (request) variable.

About the route greetings , it uses GreetingModel.query(on: app.db).all() that we already are familiar with and returns a GreetingModel array.

About the route greetings/add , receives a JSON on POST request body, that will decode using req.content.decode(GreetingModel.self) and store on the database using record.create(on: bd) and finally take the saved object (now with property public var id: Int? with the key and return everything on the response body .map { record } .

Testing POST greetings/add

Back to RESTed app, we did a POST on the route greetings/add with a greeting on the body (notice that we are not sending the greeting id) and if your look back at the code (below) we are saving our greeting and returning the greeting back (now with the id filled).

app.post("greetings", "add") { req -> EventLoopFuture<GreetingModel> in

let record = try req.content.decode(GreetingModel.self)

let db = Bool.random() ? req.db : app.db

return record.create(on: db).map { record }

}

Image 13

Testing GET greetings

Back to RESTed app, we did a GET on the route greetings and we received all stored greetings!

Image 14

IV — Extra notes

wait() and EventLoop

TLDR: .wait() is super handy for debug, but we cant use it on routing.

We know that GreetingModel.query(on: bd).all() will return a EventLoopFuture and EventLoopFuture principle is:

all callbacks registered on an `EventLoopFuture` will execute on the thread corresponding to the event loop that created the `Future`. If the `EventLoopFuture` resolves with a value, that value is returned from `wait()`

Image 15

Now notice the diference between GreetingModel.query(on: bd).all() that we used on func routes(_ app: Application) and GreetingModel.query(on: bd).all().wait() used on configure(_ app: Application) (for debug purposes).

func configure(_ app: Application) throws {

...

if let results = try? GreetingModel.query(on: app.db).all()

.wait() {

print("--------------------")

print(results as Any)

}

vs

func routes(_ app: Application) {

...

app.get("greetings") { req -> EventLoopFuture<[GreetingModel]> in

let db = Bool.random() ? req.db : app.db

return GreetingModel.query(on: db).all()

}

}

While starting the application, we where on the main tread, so there was no “problem” blocking it for a while to check if everything was OK with our database. This way, there was no problem using the .wait() command. How ever, if the try do to same thing inside routers(_ app: Application) :

app.get("greetings_crash") { req -> EventLoopFuture<[GreetingModel]> in

try? GreetingModel.query(on: app.db).all().wait()

return GreetingModel.query(on: req.db).all()

}

Image 16

We will crash (image 16), and this is why:

`wait()` will block whatever thread it is called on, so it must not be called on event loop threads: it is primarily useful for testing, or for building interfaces between blocking and non-blocking code.

References

V — Materials

All materials for this article can be found at here.

Here is current state of my web app (it’s more advanced than what you can find on this article)