Week 3
News
This week, Vapor passed Perfect and has earned the title of “Most Stars of Any Server-side Swift framework on Github”! 🎉🎉🎉🎉 Congrats to the team!
New Features
Helper Methods to Decode Content From Request Bodies
It’s very common to need to take the body of a request and decode it to an object. To cut down on this common boilerplate, new methods were introduced which take care of it automatically with any HTTP method that accepts a body:
router.post(LoginRequest.self, at: "login") { req, loginRequest -> Response in
print(loginRequest.email) // user@vapor.codes
print(loginRequest.password) // don't look!
return req.makeResponse()
}
Case Insensitive Routing
While routing is still case-sensitive by default, it is now possible to choose case-insensitive routing instead:
// configure.swift
public func configure(
_ config: inout Config,
_ env: inout Environment,
_ services: inout Services
) throws {
// Register routes to the router
let router = EngineRouter(caseInsensitive: true) // or false
try routes(router)
services.register(router, as: Router.self)
}
// routes.swift
router.get("hello") { req -> HTTPStatus in
return .ok
}
Case-sensitive
GET /hello -> 200 OK
GET /Hello -> 404 Not Found
Case-insensitive
GET /hello -> 200 OK
GET /Hello -> 200 OK
Note: The caseInsensitive
parameter to EngineRouter()
is brand-new and will not be usable until Vapor beta 4.
Value Models
struct
s can now be used as Fluent Models.
Community Contributions
Session Redirects
@0xTim introduced middleware to the Auth package that makes it easy to redirect users to another page if they aren’t authenticated. For example, if a user attempts to visit a private page without logging in, you may want to redirect them to /login
instead. This replicates the functionality of Vapor 2’s RedirectMiddleware
.
Dependency Updates
With the start of tagging, keeping Package.swift
files up-to-date is an important but tedious task. If you’re looking to pitch in, but don’t know where to start, plenty of help is needed in this area. There are going to be a lot of these, so we’re going to group them all together:
vapor/fluent-postgresql#13
vapor/leaf#90
vapor/mysql#123
vapor/postgresql#16
vapor/fluent-postgresql#14
vapor/vapor#1498
vapor/mysql#124
Fixed DatabaseConnection Bug
@bensyverson caught that database-kit
was making a bad assert (==
vs !=
)
Add Providers to OpenSSL
Adding providers to the ctls
package allows SPM to let users know when they’re missing a dependency - @Yasumoto
Fix Issue Where Large Responses Hang
With larger HTTP bodies, the continueBuffer
state was not being handled correctly, causing the last chunk to not be sent and responses to hang. This bug existed in both Vapor 2 and 3. Thanks! @kevinup7
Redundancy Cleanup
@Cellane was able to cut out a couple lines of redundant code. Any cleanup is always appreciated :)
Allow Users of MultipartForm To Set Max Request Size
The maximum request size used to be locked at 1,000,000 bytes, but @bensyverson’s PR allows users to increase this value if they need something larger.
Fixes Packet Length and SequenceID
This fix by @SandorDobi should resolve a couple MySQL bugs.
Add delete(on:)
and save(on:)
to Future
@twof merged in a couple of convenience extensions on Future
that make saving and or deleting a model much cleaner. From
return Reservation
.query(on: databaseConnectable)
.filter(\Reservation.user == userName)
.first()
.unwrap(or: Abort(.notFound, reason: "No reservations have been made"))
.map(to: Reservation.self) { reservation in
return reservation.delete(on: databaseConnectable).transform(to: reservation)
}.map(to: HTTPStatus.self) { _ in
return .ok
}
to
return Reservation
.query(on: databaseConnectable)
.filter(\Reservation.user == userName)
.first()
.unwrap(or: Abort(.notFound, reason: "No reservations have been made"))
.delete(on: databaseConnectable)
.map(to: HTTPStatus.self) { _ in
return .ok
}
Fatal Error Cleanup
As Vapor gets closer to stability, fatal errors need to be removed so that servers aren’t crashing in production. @labradon got the project well on its way by eliminating 29 fatal errors and replacing them with their proper thrown errors. (33 LoC)
Update Vapor Toolbox For Most Recent Version of SPM
There were a few deprecated options that made the toolbox unusable for a bit, but all is good again! - @gwynne
Improved Error Message
@gwynne updated error messages when hostname and port are misconfigured, which ought to make debugging a bit easier.
Benchmarks
17k Queries Per Second Per Core Achieved With Fluent/MySQL
That should scale infinitely with the number of cores too. Awesome!
https://twitter.com/JoannisOrlandos/status/964792631875244034
Talks
Joannis @ Swift Amsterdam
In this talk @Joannis will cover the upcoming Vapor 3 release - a complete redesign aimed to be more configurable and performant with less boilerplate. He’ll also talk about dependency inversion, the reactive pattern, Codable
, and how these all fit together.
https://swift.amsterdam/talks/serverside-joannis-orlandos.html
Tutorials
Ray Wenderlich Video Course and Upcoming Book by @0xtim
In this 30-video course, you’ll use Vapor, a server-side Swift web framework, to create a complex application that you can communicate with via an API and a website. The first 4 videos are available now!
https://www.raywenderlich.com/186386/new-course-server-side-swift-with-vapor
Pre-orders also opened up for the Ray Wenderlich Vapor 3 book, written by the core Vapor team themselves! This book starts with the basics of web development and introduces the basics of Vapor; it then walks you through creating APIs and web backends, creating and configuring databases, deploying to Heroku, AWS, or Docker, testing your creations, and more!
https://store.raywenderlich.com/products/server-side-swift-with-vapor
How To Set Up a Vapor 3 Project
In this tutorial @martinlasek takes you through all the steps needed to get you from installing Xcode to making requests to your Vapor server.
https://medium.com/@martinlasek/tutorial-how-to-set-up-a-vapor-3-project-75466394cf2e
Microtutorial
This week we’re going to take a look at how to wrap up our .map()
s to make promise chains a little more readable. We’ll be continuing from last week’s microtutorial:
router.get("user", UUID.parameter) { (req) -> Future<Response> in // Take in a request
let userID = try req.parameter(UUID.self) // Turn that request into an ID
return try User
.find(userID, on: req) // Use that ID to search the database for a row, turn that row into an (optional) object
.map(to: User.self) { (user) in // Unwrap the object
guard let user = user else {throw Abort(.notFound, reason: "User Not Found")}
return user
}.map(to: User.self) { (user) in // Make some change to the object
user.name = "Jamie"
return user
}.encode(for: req) // Turn that object into a response, and return that response
}
Let’s see how simple we can make this route! First we’ll conform User
to Parameter
so we can eliminate the find step.
extension User: Parameter {}
router.get("user", User.parameter) { (req) -> Future<Response> in // Take in a request `/user/<UUID of a User>
let user = try req.parameter(User.self) // User found with UUID. Aborts if no user is found
return try user
.map(to: User.self) { (user) in // Make some change to the object
user.name = "Jamie"
return user
}.encode(for: req) // Turn that object into a response, and return that response
}
With an extension to Future
we can remove that last .map()
extension Future where T == User {
public func changeName(to name: String) -> Future<Expectation> {
return self.map(to: User.self) { (user) in
user.name = name
return user
}
}
}
And our final route is quite a bit more readable!
router.get("user", User.parameter) { (req) -> Future<Response> in // Take in a request `/user/<UUID of a User>
let user = try req.parameter(User.self) // User found with UUID. Aborts if no user is found
return try user
.changeName(to: "Jamie") // Make some change to the object
.encode(for: req) // Turn that object into a response, and return that response
}