Server Side Swift with Vapor 2 (Part 2)
In the previous part of this series, we have created a RESTful API server which manipulates three models — — Lesson
, Teacher
, and Student
. In this article, I am going to build up a sibling relationship between Teacher
and Lesson
, and it is possible to follow the same pattern to achieve the relationship between Student
and Lesson
.
Introduction
Basically, we need to define the following three endpoints:
/teachers/teacher_id/teaches/lesson_id
: Set a sibling relationship betweenTeacher
andLesson
via a POST request./teachers/teacher_id/lessons
: Fetch allLesson
objects corresponding to a specificTeacher
object./lessons/lesson_id/teachers
: Fetch allTeacher
objects corresponding to a specificLesson
object.
Implementation
First of all, in order to describe a sibling relationship, it is necessary to store the pair of models’ identifiers into a new table called Pivot
. Therefore, let's move to the Config+Setup.swift
file and add the following line into the setupPreparations
method.
private func setupPreparations() throws {
// ...
preparations.append(Pivot<Teacher, Lesson>.self)
}
Secondly, we also need to define a new endpoint, in order to save the identifier pair into our Pivot
table via a POST request. Thus, switch to TeahcerController.swift
file and add the following two new methods.
func addRoutes(_ drop: Droplet) {
let teachersGroup = drop.grouped("teachers")
teachersGroup.post(Teacher.parameter, "teaches", Lesson.parameter, handler: teaches)
}private func teaches(request: Request) throws -> ResponseRepresentable {
let teacher = try request.parameters.next(Teacher.self)
let lesson = try request.parameters.next(Lesson.self)
let pivot = try Pivot<Teacher, Lesson>(teacher, lesson)
try pivot.save() return teacher
}
The reason why we have to create the addRoutes
method is this endpoint doesn't belong to the RESTful API design diagram. In other words, we need this method to connect with the Droplet
object. So, open the Routes.swift
file and modify the setupRoutes
method.
func setupRoutes() throws {
// ...
let teacherController = TeacherController()
resource("teachers", teacherController)
teacherController.addRoutes(self) // ...
}
At this point, we are able to generate a sibling relationship between Teacher
and Lesson
. Let's continue implementing how to retrieve the relevant model objects. In TeacherController.swift
file, add another new method and a new endpoint. In addition, create an extension of Teacher
to get the sibling of Lesson
easily.
final class TeacherController {
// ... func addRoutes(_ drop: Droplet) {
let teachersGroup = drop.grouped("teachers")
// ...
teachersGroup.get(Teacher.parameter, "lessons", handler: lessons)
} private func lessons(request: Request) throws -> ResponseRepresentable {
let teacher = try request.parameters.next(Teacher.self)
return try teacher.lessons().makeJSON()
}
}// ...extension Teacher {
func lessons() throws -> [Lesson] {
let lessons: Siblings<Teacher, Lesson, Pivot<Teacher, Lesson>> = siblings()
return try lessons.all()
}
}
Next, we add the following methods and an extension of Lesson
within LessonController.swift
, in order to fetch the corresponding Teacher
sibling.
final class LessonController {
// ... func addRoutes(_ drop: Droplet) {
let lessonsGroup = drop.grouped("lessons")
lessonsGroup.get(Lesson.parameter, "teachers", handler: teachers)
} private func teachers(request: Request) throws -> ResponseRepresentable {
let lesson = try request.parameters.next(Lesson.self)
return try lesson.teachers().makeJSON()
}
}// ...extension Lesson {
func teachers() throws -> [Teacher] {
let teachers: Siblings<Lesson, Teacher, Pivot<Teacher, Lesson>> = siblings()
return try teachers.all()
}
}
Finally, remember to hook up the new endpoint with the Droplet
object in Routes.swift
file.
func setupRoutes() throws {
let lessonController = LessonController()
resource("lessons", lessonController)
lessonController.addRoutes(self) // ...
}
Conclusion
The entire sample code is here.
Again, we can test our new endpoints with Postman. However, let’s talk about the pros and cons of Vapor. On one hand, the merits include well-defined documentations, useful built-in functions, and a migration tool. On the other hand, the downsides are fewer search results for Vapor 2 than 1, and Swift keeps evolving. Still, I personally think Vapor is a good opportunity to learn back-end development for a iOS developer. I’m totally open to discussion and feedback, so please share your thoughts.