Building Your Web App in Swift Using Vapor and Postgres
In WWDC 2015, Apple announced that Swift would be open source. Shortly after that, in December 2015, Swift’s codebase was public on GitHub.
Open sourcing the Swift codebase introduces developers to a multitude of opportunities and expand the use of Swift worldwide. With its change to Open Source, it didn’t take long before a slew of new technologies based on Swift began popping up all over the place, especially for the web. Today, the most frequently used web framework for Swift is Vapor, and it is widely considered a good choice for web development because it is fast, secure, extensible and also has integration with XCode.
In this Tutorial, we will learn the basics of how to build an web app using Swift.
System requirements
To install and run Vapor, your development environment must meet these minimum requirements:
- Operating Systems: macOS (64-bit)
- Disk Space: 1.17 GB (does not include disk space for IDE/tools).
Prerequisites
Xcode 9.3
Install Xcode 9.x from Mac App Store
Vapor Toolkit
The toolbox includes all of Vapor’s dependencies as well as a handy CLI tool for creating new projects.
brew install vapor/tap/vapor
Tip
If you don’t already have Homebrew installed, install it at brew.sh →
Verify Installation
Double check the installation was successful by running:
vapor version
Creating a Project
To create a new project, you must provide Vapor with a project name. For this guide, we will call the project HelloVapor
. First, navigate to a directory where you would want to create the new project, then run the following command:
vapor new HelloVapor
Vapor will create a project called HelloVapor
in your current directory. Navigate to your new project directory.
cd HelloVapor
You should see a directory similar to the following:
First, Vapor creates the Package.swift
file. There is no need to modify it. Then there is the Sources
folder, which contains App
& Run
folders. The main.swift
file is inside Run
folder. The main.swift
file is our main app file, and where we initialize our server. Vapor will run this file first.
Registering Routes In Your WebApp
Now with XCode open, go to the file Sources/App/routes.swift
. There you will find methods of router.
But, what is a Route? A route is a declaration of a path that is mapped to a handler.
You will note that we already have a route declared. This route points to the root directory and loads a default welcome page created by Vapor:
/// Register your application's routes here.public func routes(_ router: Router) throws {// Basic "Hello, world!" examplerouter.get("hello") { req inreturn "Hello, world!"}// Example of configuring a controllerlet todoController = TodoController()router.get("todos", use: todoController.index)router.post("todos", use: todoController.create)router.delete("todos", Todo.parameter, use: todoController.delete)}
Generate Xcode Project
Let’s create our Xcode project. After all, what’s the advantage to building a Swift project without XCode?
Navigate to your new project directory HelloVapor
. Let’s now use the Vapor Toolbox’s xcode
command to generate an Xcode project. This will allow us to build and run our app from inside of Xcode, just like an iOS app.
vapor xcode
The toolbox will ask you if you’d like to open Xcode automatically, select yes
.
Build & Run
You should now have Xcode open and running. Select the run scheme from the scheme menu and My Mac as the deployment target, then click the play button.
You should see the terminal pop up at the bottom of the screen.
Server starting on http://localhost:8080
Visit Localhost
Open your web browser, and visit localhost:8080/hello →
You should see the following page.
Hello, world!
Persisting Information
One of the most essential capabilities of an App is persisting information. Vapor supports some of the best persistence technologies — like MySQL, PostgreSQL, MongoDB, SQLite and Redis — through the use of providers.
The Provider protocol creates a simple and predictable way to add functionality and third party packages to your Vapor project.
In this project, we will utilize PostgreSQL, but feel free to use any other technology you may be more comfortable with.
Configuring Your PostgreSQL Server
- PostgreSql 10.x
Download the installer ➜ Move to Applications folder ➜ Double Click ➜ Click “Initialize” to create a new server
- pgAdmin
Download the installer ➜ Move to Applications folder ➜ Double Click
Create Your Database
Open pgAdmin 4.x
In the Add New Server Dialog, enter a server name in the General tab, I chose “TodoApi”. In the Connection tab you can enter localhost for the hostname and type a password of your choice. For this example my password will be password. You can leave the other fields as their default values.
Link your project with PostgreSQL
So far so good, but now let’s configure the database on our Vapor project. We will have to replace FluentSQLite by FluentPostgreSQL within Package.swift:
// swift-tools-version:4.0import PackageDescriptionlet package = Package(name: "HelloVapor",dependencies: [// 💧 A server-side Swift web framework..package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),// 🖋🐘 Swift ORM (queries, models, relations, etc) built on PostgreSQL..package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0-rc.4.1") // changed],targets: [.target(name: "App", dependencies: ["FluentPostgreSQL", "Vapor"]),.target(name: "Run", dependencies: ["App"]),.testTarget(name: "AppTests", dependencies: ["App"])])
It may take a bit fetching the dependency, but when done you should have FluentPostgreSQL & PostgreSQL frameworks in Dependencies group in project structure like this:
Before we can run our project we’ll have to configure PostgreSQL. Let’s go 🚀
Configure your project to use PostgreSQL
Our first step is to change everything FluentSQLite related to our new FluentPostgreSQL in our Todo model Todo.swift:
import FluentPostgreSQL // changedimport Vapor/// A single entry of a Todo list.final class Todo: PostgreSQLModel {/// The unique identifier for this `Todo`.var id: Int?/// A name describing what this `Todo` entails.var Name: String/// A bool describing if this `Todo` is completed.var IsComplete: Bool/// Creates a new `Todo`.init(id: Int? = nil, name: String, isComplete: Bool) {self.id = idself.Name = nameself.IsComplete = isComplete}}/// Allows `Todo` to be used as a dynamic migration.extension Todo: Migration { }/// Allows `Todo` to be encoded to and decoded from HTTP messages.extension Todo: Content { }/// Allows `Todo` to be used as a dynamic parameter in route definitions.extension Todo: Parameter { }
The second step before we can run our project is to register the database information within our configure.swift:
import Vaporimport FluentPostgreSQL // added/// Called before your application initializes.public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {/// Register providers firsttry services.register(FluentPostgreSQLProvider()) // changed/// Register routes to the routerlet router = EngineRouter.default()try routes(router)services.register(router, as: Router.self)/// Register middlewarevar middlewares = MiddlewareConfig() // Create _empty_ middleware config/// middlewares.use(FileMiddleware.self) // Serves files from `Public/` directorymiddlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP responseservices.register(middlewares)// Configure a Postgres databaselet postgresqlConfig = PostgreSQLDatabaseConfig(hostname: "127.0.0.1",port: 5432,username: "postgres",database: "TodoList",password: nil)services.register(postgresqlConfig)/// Configure migrationsvar migrations = MigrationConfig()migrations.add(model: Todo.self, database: .psql) // changedservices.register(migrations)}
If you now Cmd
+R
or Run we should be able to access the /todos route on http://localhost:8080/todos 😊!
Your database should now be updated in PgAdmin. You may have to refresh the database to see the changes.
Where to Go From Here?
You can perform CRUD operations on the /todos route & download the completed project using the link at the bottom of this tutorial.
As always, any feedback is appreciated, so feel free to comment down here or reach out on twitter — and, as always,