Using Vapor and Fluent to create a REST API

Using Vapor and Fluent to create a REST APIRadu DanBlockedUnblockFollowFollowingApr 5Swift is awesome.

Yes, it’s mature (now with 5.

0 we have ABI stability, hooray!).

You have the power of OOP, POP, functional and imperative programming in your hands.

You can do almost anything in Swift nowadays.

If you ever thought of being a full stack developer with knowing both backend and frontend, then this article is for you.

The most known web frameworks written in Swift are Kitura and Vapor.

Vapor is now at version 3 (released in May, 2018), is open source and you can easily create your REST API, web application or your awesome website.

In this tutorial you will learn:how to get started with Vaporcreate your first REST APIhow to use Fluent ORM Frameworkhow to transform 1:M and M:M db relationships to parent-child or siblings relationships in Fluentapply what you learn in a real scenario exampleIf you want to skip this part, the whole project is found on GitHub:radude89/footballgather-wsContribute to radude89/footballgather-ws development by creating an account on GitHub.

github.

comPrerequisitesFor this tutorial you will need:Xcode 10.

2Knowledge of SwiftBasic knowledge of REST APISome knowledge of Swift Package ManagerGetting startedFirst, you need to install Xcode from Mac App Store.

You can use brew to install Vapor Toolbox.

This is useful so we can run command line tasks for common operations.

/usr/bin/ruby -e "$(curl -fsSL https://raw.

githubusercontent.

com/Homebrew/install/master/install)"brew tap vapor/tapbrew install vapor/tap/vaporYou are ready to go!Football Gather — iOS App ExampleFootballGather is a demo project for friends to get together and play football matches as quick as possible.

You can imagine the client app by looking at this mockups (created with Balsamiq):FootballGather sample iOS App — Mockups created with BalsamiqFeatures:Persist playersAbility to add playersSet countdown timer for matchesUse the application in offline modePersist playersDatabase StructureLet’s use a database schema like in the image below:In this way we can exemplify the 1:M relationship between users and gathers, where one user can create multiple gathers and M:M Player to Gather, where a gather can have multiple players and a player can play in multiple gathers.

List of controllersIf we look at the iOS app, we will create the following controllers:UserControllerPOST /api/users/login — Login functionality for usersPOST /api/users — Registers a new userGET /api/users — Get the list of usersGET /api/users/{userId} — Get user by its idDELETE /api/users — Get the list of usersPlayerControllerGET /api/players — Gets the list of playersGET /api/players/{playerId} — Gets the player by its idGET /api/players/{playerId}/gathers — Gets the list of gathers for the playerPOST /api/players — Adds a new playerDELETE /api/players/{playerId} — Deletes a player with a given idPUT /api/players/{playerId} — Updates a player by its idGatherControllerGET /api/gathers — Gets the list of gathersGET /api/gathers/{gatherId} — Gets the gather by its idGET /api/gathers/{gatherId}/players — Gets the list of players in the gather specified by idPOST /api/gathers/{gatherId}/players/{playerId} — Adds a player to the gatherPOST /api/gathers — Adds a new gatherDELETE /api/gathers/{gatherId} — Deletes a gather with a given idPUT /api/gathers/{gatherId} — Updates a gather by its idApp StructureOpen the Xcode project that you created in previous section.

Type:vapor xcode -yThis may take a while.

Here are the generated files:├── Public├── Sources│ ├── App│ │ ├── Controllers│ │ ├── Models│ │ ├── boot.

swift│ │ ├── configure.

swift│ │ └── routes.

swift│ └── Run│ └── main.

swift├── Tests│ └── AppTests└── Package.

swiftWhat you will be touching in this project:Package.

swiftThis is the manifest of the project and defines all dependencies and the targets of our app.

I am using Vapor 3.

3.

0.

You can change Package.

swift as below:.

package(url: “https://github.

com/vapor/vapor.

git", from: “3.

3.

0”)PublicAll the resources that you want to make them public, such as images.

SourceHere you can see two separate modules: App and Run.

You usually have to put all of your developed code inside “App”.

The Run folder contains the main.

swift file.

ModelsAdd here your Fluent models.

In our app: User, Player, Gather.

ControllersThe controller is where you write the logic of your REST API, such as CRUD operations.

Similar with iOS ViewControllers, but instead they handle the requests and manage the models.

routes.

swiftUsed to find the appropriate response for an incoming request.

configure.

swiftCalled before app is initialised.

Register router, middlewares, database and model migrations.

Implementing the UserControllerBefore starting to implement our user controller, remove the generated Todo related code:TodoController.

swift from controllers folder.

Todo.

swift from Models.

Line migrations.

add(model: Todo.

self, database: .

sqlite)) from configure.

swiftAll that is found in routes function from routes.

swift.

A user will be defined by a username and a password.

The primary key will be of type UUID representing a unique String.

Create a new file, User.

swift and add it to the Models folder.

Add in the file a class called User.

Make it comply to the following protocols:Codable: Map the parameters of the service to the actual class parameters.

SQLiteUUIDModel: Convenience helper protocol to make the Model as anSQLite Model class with a UUID as primary key.

Used for compilation safety for referring to properties.

Content: Used to easy decode the information with VaporMigration: Tells Fluent how to configure the database.

Parameter: Used for requests with parameters, such as GET /users/{userId}.

Now you have your User model.

Let’s create the UserController.

Create a new struct called UserController inside Controllers folder.

Make it comply to RouteCollection protocol.

Leave the boot function for now.

Next we are going to add the CRUD operations for Users.

GET all usersfunc getHandler(_ req: Request) throws -> Future<User> { return try req.

parameters.

next(User.

self)}This will extract the user id from the request and query the database to return the User.

CREATE userfunc createHandler(_ req: Request, user: User) throws -> Future<Response> { return user.

save(on: req).

map { user in var httpResponse = HTTPResponse() httpResponse.

status = .

createdif let userId = user.

id?.

description { let location = req.

http.

url.

path + "/" + userId httpResponse.

headers.

replaceOrAdd(name: "Location", value: location) }let response = Response(http: httpResponse, using: req) return response }}We are going to use save function that returns a user object.

Following REST API standard, we extract the created UUID of the user and return as part of the Location header of the response.

DELETE userfunc deleteHandler(_ req: Request) throws -> Future<HTTPStatus> { return try req.

parameters.

next(User.

self).

flatMap(to: HTTPStatus.

self) { user in return user.

delete(on: req).

transform(to: .

noContent) }}First we extract the user id from the request parameters.

We perform delete function on the user and return a HTTPStatus associated with no content.

Implementing PlayerControllerPlayerController follows the same pattern as UserController.

The extra function in this case consists of update part.

func updateHandler(_ req: Request) throws -> Future<HTTPStatus> { return try flatMap(to: HTTPStatus.

self, req.

parameters.

next(Player.

self), req.

content.

decode(Player.

self)) { player, updatedPlayer in player.

age = updatedPlayer.

age player.

name = updatedPlayer.

name player.

preferredPosition = updatedPlayer.

preferredPosition player.

favouriteTeam = updatedPlayer.

favouriteTeam player.

skill = updatedPlayer.

skillreturn player.

save(on: req).

transform(to: .

noContent) }}If we look at this function, we first extract the player id for the player that we want to perform an update.

Next, we extract all of the properties and map them to a Player object.

We perform the update and as you might guessed it we call save method.

Register functionsfunc boot(router: Router) throws { let playerRoute = router.

grouped("api", "players") playerRoute.

get(use: getAllHandler) playerRoute.

get(Player.

parameter, use: getHandler) playerRoute.

post(Player.

self, use: createHandler) playerRoute.

delete(Player.

parameter, use: deleteHandler) playerRoute.

put(Player.

parameter, use: updateHandler) playerRoute.

get(Player.

parameter, "gathers", use: getGathersHandler)}For GatherController we stick to the same pattern as for UserController.

1:M and M:M relationshipsIn order to implement a relationship between two model classes we will have to create a Pivot class.

final class PlayerGatherPivot: SQLiteUUIDPivot { var id: UUID?.var playerId: Player.

ID var gatherId: Gather.

ID var team: Stringtypealias Left = Player typealias Right = Gatherstatic var leftIDKey: LeftIDKey = PlayerGatherPivot.

playerId static var rightIDKey: RightIDKey = PlayerGatherPivot.

gatherIdinit(playerId: Player.

ID, gatherId: Gather.

ID, team: String) { self.

playerId = playerId self.

gatherId = gatherId self.

team = team }}// Player.

swiftextension Player { var gathers: Siblings<Player, Gather, PlayerGatherPivot> { return siblings() }}// Gather.

swiftextension Gather { var players: Siblings<Gather, Player, PlayerGatherPivot> { return siblings() }}The implementation from above describes the M:M relationship between players and gathers.

We use the left key as the primary key for players table and the right key as the primary key for gathers.

This is similar as a primary key composed of FK/PK for a M:M relationship.

The ‘team’ attribute describes the team in which the player is member in the current gather.

We will have to specify the siblings inside our model classes.

This is done using Generic principle from Swift.

For a 1:M relationship we can look at User v Gather:final class Gather: Codable { var userId: User.

ID .

}extension Gather { var user: Parent<Gather, User> { return parent(.

userId) }}Inside our controller classes the methods can be seen below:extension GatherController { func getPlayersHandler(_ req: Request) throws -> Future<[Player]> { return try req.

parameters.

next(Gather.

self).

flatMap(to: [Player].

self) { gather in return try gather.

players.

query(on: req).

all() } }}extension PlayerController { func getGathersHandler(_ req: Request) throws -> Future<[Gather]> { return try req.

parameters.

next(Player.

self).

flatMap(to: [Gather].

self) { player in return try player.

gathers.

query(on: req).

all() } }}Registering routes and configuring databaseOpen routes.

swift and add the following inside routes function:let userController = UserController()try router.

register(collection: userController)let playerController = PlayerController()try router.

register(collection: playerController)let gatherController = GatherController()try router.

register(collection: gatherController)These lines will register all of your controllers.

In configure.

swift add all of your models in the MigrationsConfig:migrations.

add(model: User.

self, database: .

sqlite)migrations.

add(model: Player.

self, database: .

sqlite)migrations.

add(model: Gather.

self, database: .

sqlite)migrations.

add(model: PlayerGatherPivot.

self, database: .

sqlite)That’s it.

Build & run.

.. More details

Leave a Reply