Server Side Swift with Vapor 2 (Part 1)
Before Swift was open source in 2015, I had tried to write a simple RESTful API server with Node.js. However, I did not have enough time digging deeper because I have not been familiar with Javascript syntax and back-end development. Nowadays, there are several different server side Swift frameworks, such as Vapor, Perfect, and Kitura. As an iOS developer, it is a good chance to broaden my horizon in back-end development.
Why Vapor
According to its GitHub page, Vapor is the most used web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website, API, or cloud project. More importantly, there are series free tutorials on raywenderlich.com website. These tutorials give me a basic concept of how to build up an API server with Vapor, even though they are made in the previous version. Thank you Ray!
Introduction
In this article, I will demonstrate a simple RESTful API server which manipulates three models: Lesson
, Teacher
, and Student
. Basically, we need the following endpoints:
/{models}
: Fetch all model objects via a GET request or create a new model object via a POST request with a JSON body. (Just replace {models} with lessons, teachers, and students.)/{models}/id
: Fetch a specific model object via a GET request, delete one model object via a DELETE request, or update an existing model object via a PATCH request with a JSON body.
Configuration
Before we dive into coding, there are several necessary configurations. First of all, follow the instructions on Vapor’s website to install Vapor correctly, and then use vapor new your_project_name --template=api
to create a new project. Secondly, I choose to use PostgreSQL as my database server in this project, so it is necessary to install PostgreSQL on my Macbook and add the PostgreSQL provider for Vapor into my project dependencies. After starting PostgreSQL server, open and modify the Package.swift
file as following.
let package = Package(
// ...
dependencies: [
.Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2),
.Package(url: "https://github.com/vapor-community/postgresql-provider.git", majorVersion: 2)
],
// ...
)
Then, create a new file called postgresql.json
under the directory Config/secrets
and add the following code snippet into the postgresql.json
file.
{
"hostname": your_host_address,
"user": your_username,
"password": your_password,
"database": your_database_name,
"port": 5432
}
Remember to replace the driver within Config/fluent.json
with the following.
"driver": "postgresql"
The final step is to add the PostgreSQL provider within the Config+Setup.swift
file.
import PostgreSQLProviderextension Config {
// ...
private func setupProviders() throws {
try addProvider(FluentProvider.Provider.self)
try addProvider(PostgreSQLProvider.Provider.self)
}
// ...
}
After finishing all configurations, use vapor build
and vapor xcode
to fetch the necessary dependencies and generate a new Xcode project.
Implementation
Let’s handle our model class at first. Open the Xcode project that we generate at the previous step, and add Lesson.swift
, Teacher.swift
, and Student.swift
files into the Models
group. When adding a new file to our project, make sure the choose the target as App.
Moreover, follow Vapor’s document to define our model properly. Because their implementations are quite similar, I just show Lesson.swift
as following.
import PostgreSQLProviderfinal class Lesson: Model {
let storage = Storage() // This is for Storable protocol var title: String // Use these keys instead of magic strings
static let idKey = "id"
static let titleKey = "title" init(title: String) {
self.title = title
} init(row: Row) throws {
self.title = try row.get(Lesson.titleKey)
} func makeRow() throws -> Row {
var row = Row()
try row.set(Lesson.titleKey, title)
return row
}
}// For database prepare and revert
extension Lesson: Preparation {
static func prepare(_ database: Database) throws {
try database.create(self) { (user) in
user.id()
user.string(Lesson.titleKey)
}
} static func revert(_ database: Database) throws {
try database.delete(self)
}
}// Convenience of generate model from JSON
extension Lesson: JSONConvertible {
convenience init(json: JSON) throws {
self.init(title: try json.get(Lesson.titleKey))
} func makeJSON() throws -> JSON {
var json = JSON()
try json.set(Lesson.idKey, id?.string)
try json.set(Lesson.titleKey, title)
return json
}
}// Convenience of returning response
extension Lesson: ResponseRepresentable {}
After creating our models, switch to Config+Setup.swift
file and add the preparations.
private func setupPreparations() throws {
preparations.append(Lesson.self)
preparations.append(Teacher.self)
preparations.append(Student.self)
}
Next, add the controllers corresponding to our models in the Controllers
group as well. Inside each controller, we can take the advantages of Vapor's ResourceRepresentable
protocol to deal with model's CRUD. Again, for the simplicity, I just display the implementation of LessonController.swift
as following.
import PostgreSQLProviderfinal class LessonController {
fileprivate func getAll(request: Request) throws -> ResponseRepresentable {
return try Lesson.all().makeJSON()
} fileprivate func getOne(request: Request, lesson: Lesson) throws -> ResponseRepresentable {
return lesson
} fileprivate func create(request: Request) throws -> ResponseRepresentable {
let lesson = try request.lesson()
try lesson.save()
return lesson
} fileprivate func update(request: Request, lesson: Lesson) throws -> ResponseRepresentable {
let newLesson = try request.lesson()
lesson.title = newLesson.title
try lesson.save()
return lesson
} fileprivate func delete(request: Request, lesson: Lesson) throws -> ResponseRepresentable {
try lesson.delete()
return lesson
}
}// Notice the difference between Item and Muliple
extension LessonController: ResourceRepresentable {
func makeResource() -> Resource<Lesson> {
return Resource(
index: getAll,
store: create,
show: getOne,
update: update,
destroy: delete
)
}
}// Convenience of retrieving Lesson object
extension Request {
fileprivate func lesson() throws -> Lesson {
guard let json = json else { throw Abort.badRequest } return try Lesson(json: json)
}
}
Finally, hook up our controllers and the Droplet
object via adding the following line into Router.swift
file.
func setupRoutes() throws {
let lessonController = LessonController()
resource("lessons", lessonController) let teacherController = TeacherController()
resource("teachers", teacherController) let studentController = StudentController()
resource("students", studentController)
}
Where To Go From Here
At this point, we achieve a simple RESTful API server, and we can test model CRUD with Postman. In the part 2 of this series, I will demonstrate how to implement a sibling (many to many) relationship between two models.