Posted 7/5/2018.
In Part 1 we saw how to install Vapor and create an Xcode project using the default api
template. That project comes with a lot of code demonstrating the basics of building an API in Vapor. It uses SQLite as the database and a Todo
model and controller as an example. Our app will eventually use PostgreSQL and will model information about U.S. national parks.
The first thing we'll do in this tutorial is clean up the template, removing all of the code we don't need and the SQLite dependency. We'll then go through the basic setup of a Vapor app.
We'll start by removing the unnecessary code and then we'll go through what's left in detail.
Delete the Todo.swift
and TodoController.swift
files, but keep the Models
and Controllers
folders.
Replace routes.swift
with the following:
import Vapor
public func routes(_ router: Router) throws {
router.get("hello", use: sayHelloWorld)
}
private func sayHelloWorld(_ request: Request) throws -> String {
guard request.environment != .testing else {
throw Abort(.badRequest, reason: "Route should not be called in Testing environment.")
}
return "Hello, world!"
}
configure.swift
with the following:import Vapor
public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
let router = EngineRouter.default()
try routes(router)
services.register(router, as: Router.self)
var middlewares = MiddlewareConfig()
middlewares.use(ErrorMiddleware.self)
services.register(middlewares)
}
Package.swift
with the following:// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "NationalParks",
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0")
],
targets: [
.target(name: "App", dependencies: ["Vapor"]),
.target(name: "Run", dependencies: ["App"]),
.testTarget(name: "AppTests", dependencies: ["App"])
]
)
All that changed here is we removed the SQLite dependency.
In Terminal, make sure you're in the root directory of the project. Then run vapor update
to update the project based on the changes we just made to Package.swift
. You will be prompted to regenerate and open the Xcode project. Enter y
for at both prompts. You should see that the SQLite dependency has been removed.
Finally, Vapor projects have many dependencies that we will never need to build ourselves, so it's helpful to remove their related schemes from the list we see in Xcode. Click on the scheme dropdown and go to "Manage Schemes". Uncheck all boxes except NationalParks-Package
and Run
. Now when you click on the schemes dropdown those are the only two you should see.
Vapor apps start in main.swift
, which contains one line:
try app(.detect()).run()
All this does is call the global app
function, which accepts one argument for the environment in which we're running the app (e.g. production, development, or testing). We'll see how to use environments when we run the app from the command line below.
Next look at app.swift
, which defines the function called from main.swift
.
public func app(_ env: Environment) throws -> Application {
var config = Config.default()
var env = env
var services = Services.default()
try configure(&config, &env, &services)
let app = try Application(config: config, environment: env, services: services)
try boot(app)
return app
}
The Vapor framework includes a default configuration and set of services to use in our application. The app
function uses the environment that was passed in and these defaults to configure and then initialize the application. Finally, we call the global boot
function to do any post-initialization setup before returning the application instance. In the default api
template, this boot
function is empty.
Now let's look at configure.swift
.
public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
let router = EngineRouter.default()
try routes(router)
services.register(router, as: Router.self)
var middlewares = MiddlewareConfig()
middlewares.use(ErrorMiddleware.self)
services.register(middlewares)
}
Here we do two things. First, we register our routes, which we'll see in a moment. Then we add and register middleware (more on the ErrorMiddleware
below.
Finally, let's look at routes.swift
.
public func routes(_ router: Router) throws {
router.get("hello", use: sayHelloWorld)
}
private func sayHelloWorld(_ request: Request) throws -> String {
guard request.environment != .testing else {
throw Abort(.badRequest, reason: "Route should not be called in Testing environment.")
}
return "Hello, world!"
}
Here we have one route, a modified version of the /hello
route from Part 1. This is a basic GET
request, where the first argument is the path ("hello") and the second is a closure that takes a Request
and returns a String
.
In sayHelloWorld
, we guard that the environment is not the testing environment. If it is, we throw an error ("Bad Request"). Otherwise, we return the string "Hello, world!".
If you run the app and go to http://localhost:8080/hello
you should see "Hello, world!". This is because by default Vapor uses the "development" environment.
To see how the app runs in the testing environment, we'll run it from the command line. In Terminal, make sure you're in the root directory of the project and run the following:
swift run Run --env test
This command runs the application using the scheme Run
and passes in "test" for the environment argument. If you remember from main.swift
, we passed in .detect()
for the environment. To see the implementation of detect
go to CommandInput.swift
in Console > Command > Run and find the detect
function in the Environment
extension. You should see that the supported environment values are "prod", "dev", and "test".
When you run the above command, Terminal will report Server starting on http://localhost:8080
. Now in your browser, refresh http://localhost:8080/hello
. You should see the following JSON:
{"error":true,"reason":"Route should not be called in Testing environment."}
This is the error we threw in sayHelloWorld
, so we know we've successfully run the app in the testing environment. But how is Vapor converting the error to the JSON we see in the browser?
That's where ErrorMiddleware
comes in. Comment out the following line in configure.swift
:
middlewares.use(ErrorMiddleware.self)
Now run the app from the command line again in the testing environment (swift run Run --env test
).
To stop the previous run, enter control-C
If you refresh the /hello
route in the browser, this time you'll get the browser's "Can't open page" error. This is because ErrorMiddleware
is responsible for taking server errors and converting them into HTTP responses that can be viewed in the browser. Uncomment that line again, rerun the app from Terminal, and you should see the error again.
In this part, we saw how to clean up the default api
template and went through the basic structure of a Vapor app. Next, we'll look at basic routing in Vapor.