In the first part of our Tutorial, we learned how to setup and run our Vapor environment, as well as how to configure MySQL for use. If you missed Part 1, you can read it here! Now, in Part 2, we will create our data Model and learn how to develop the necessary basic data operations that will be used on our Pet App. So, let's get started!

MODEL

Model is the base protocol for any of your application's models, especially those you want to persist.

Using Model subclasses, we can get objects that represent your information, and all the complexity involved in create, delete, update and fetch commands will be resolved! All we have to do is specify the fields, make it conform to ResponseRepresentable protocol, and create preparations.

First of all, let's create a new file named Pet.swift inside Models folder.
The first step is to open this file and import:
import Vapor import Foundation

Now, we declare our class as Model protocol, like this:
final class Pet: Model { }

Let's create a few properties that will be the persisted columns on our database, not forgetting to id properties that are required when using MySQL:

var id: Node? var name: String var age: String var owner: String var lastVisit:String var specie:String var picture: String var exists: Bool = false

Now we implement the 3 needed methods:

init(name: String, age:String, owner:String, lastVisit:String, specie:String, picture:String) { self.id = nil self.name = name self.age = age self.owner = owner self.lastVisit = lastVisit self.specie = specie self.picture = picture }

init(node: Node, in context: Context) throws { id = try node.extract("id") name = try node.extract("name") age = try node.extract("age") owner = try node.extract("owner") lastVisit = try node.extract("lastVisit") specie = try node.extract("specie") picture = try node.extract("picture") }

func makeNode(context: Context) throws -> Node { return try Node(node: [ "id": id, "name": name, "age" : age, "owner": owner, "lastVisit": lastVisit, "specie" : specie, "picture" : picture ]) }

 

Code will be available on GitHub - don't worry about the code for now.

 

The first initializer is the one we will use to create our new persisted information. The second and the third methods are necessary to conform to ResponseRepresentable. With that, we have the information in Node object format, which is used by Fluent to store and retrieve information, and as an added bonus we get facilities like the ability to transform objects in JSON format.

Lastly, to complete our class, we have to implement two more methods that will handle the database tables. The first is the prepare method, responsible for creating new tables when they are not yet extent, and the second method is revert, used to delete the entire table:

static func prepare(_ database: Database) throws { try database.create("pets") { pet in pet.id() pet.string("name") pet.string("age") pet.string("owner") pet.string("lastVisit") pet.string("specie") pet.string("picture") } }

static func revert(_ database: Database) throws { try database.delete("pets") }

 

Don't be scared: revert method will not be called within your code. When you want to drop your tables, you can invoke this function via Terminal command vapor run prepare --revert.

 

Now, our Model class is done. We only need to declare our preparations together with our Droplet creation on Main.swift, like this:

let drop = Droplet( preparations:[Pet.self], providers:[VaporMySQL.Provider.self] )

CRUD OPERATIONS

Now that our Model is done, we can start our database operations. In computer programming, CRUD is the Acronym for the four basic persistent storage functions: Create, Read, Update and Delete.
For now, our server CRUD requests will be answered with JSON response, and User Interface will be the theme of Part 3 of this Tutorial. Let's open Main.swift, register new routes, and practice what we'll need to do to get the CRUD operations for our Pet App:

  • Creating a new Pet:

drop.post("pet") { request in guard let name:String = request.data["name"]?.string, let age:String = request.data["age"]?.string, let owner:String = request.data["owner"]?.string, let nextVisit:String = request.data["nextVisit"]?.string, let specie:String = request.data["specie"]?.string, let picture:String = request.data["picture"]?.string else { throw Abort.badRequest } var myPet:Pet = Pet(name: name, age: age, owner: owner, lastVisit: nextVisit, specie: specie, picture: picture) try myPet.save() return try JSON(node: Pet.all().makeNode()) }

In this first snippet, we register a POST method for an endpoint "pet", and safely store the needed parameters using Swift's guard let. If some of these parameters are missing, then we throw a Bad Request message. Then, with all parameters correct, we create our Pet instance and save through myPet.save(). Guess what??? Our Pet is saved!!!

giphy (4).gif

And to finish, we simply return all the entries on our Pet table in JSON format.

 

You can test your inserts doing a POST to a url like this: http://localhost:8080/pet?name=Bob&age=2&owner=John&lastVisit=14-May-2016&specie=Cat&picture=https://img.url . You can usePostman to make your life easier

 

  • Update an existent Pet:

drop.put("pet") { request in guard let identifier:String = request.data["id"]?.string, let name:String = request.data["name"]?.string, let age:String = request.data["age"]?.string, let owner:String = request.data["owner"]?.string, let lastVisit:String = request.data["lastVisit"]?.string, let specie:String = request.data["specie"]?.string, let picture:String = request.data["picture"]?.string else { throw Abort.badRequest } guard var myPet:Pet = try Pet.find(identifier) else { throw Abort.notFound } myPet.name = name myPet.age = age myPet.owner = owner myPet.lastVisit = lastVisit myPet.specie = specie myPet.picture = picture try myPet.save() return try JSON(node: Pet.all().makeNode()) }

The first half of the Update route is pretty similar to Insert. The primary difference is that for updates, we use PUT method instead, and also send the id to identify which entry we will update. After checking all parameters, we'll try to fetch an existent Pet. If we do not find it, then we respond throwing Not Found. With the Pet object loaded, we simply need to update with new information, and again, invoke the save method. Finally, we respond again with all Pet entries.

 

You can test your updates doing a PUT to an url like this: http://localhost:8080/pet?id=1&name=George&age=3&owner=Steven&lastVisit=14-May-2016&specie=Parrot&picture=https://img.url, where id is the Pet to update.

 

  • Deleting an existing Pet:

drop.delete("pet", String.self) { request , identifier in guard var myPet:Pet = try Pet.find(identifier) else { throw Abort.notFound } try myPet.delete() return try JSON(node: Pet.all().makeNode()) }

Our deletion route also uses the same endpoint, but with DELETE method.
In this route we explore a new possibility. Instead of passing id as a parameter, we use a type safe routing parameter String.self, accessible via the second closure parameter - in this case, identifier. It makes our code cleaner, safer and avoids some validations, once this handler alone will be executed when a valid string arrives.
To proceed with our deletion logic, we fetch the Pet with specified id using Pet.find(identifier). With the object in hand, we only need to call delete() and it'll be gone!

 

You can test your inserts doing a DELETE to a url like this: http://localhost:8080/pet/1, where the last number is the idfor the Pet to delete.

 

  • Reading Pets:

Our Pet app will demand two reading methods: One that will fetch all Pets, and other that will fetch specific Pets:

drop.get("pet") { request in return try JSON(node: Pet.all().makeNode()) }

drop.get("pet", String.self) { request , identifier in guard var myPet:Pet = try Pet.find(identifier) else { throw Abort.notFound } return try JSON(node: myPet.makeNode()) }

The first route is a GET method that simply points to pet endpoint. As in previously presented examples, it will return load all Pets in Node format through Pet.all().makeNode() and return as JSON using the JSON(...) call.

 

You can fetch all entries simple doing a GET to http://localhost:8080/pet

 

The second route, as the delete route, has a type safe routing parameter that will be accessed through the second closure parameter identifier. It tries to fetch the specific Pet through Pet.find(identifier). If nothing is found, it throws Not Found. With the loaded Pet, we return its JSON representation in the end of the method.

 

You can test your single entry fetch doing a GET to an url like this: http://localhost:8080/pet/1, where the last number represents the id of the Pet to retrieve.

 WHAT'S NEXT?

I can't believe it! Part 2 is ending!!!

source (1).gif

Don't be sad! Part 3 is coming!

In this part of the Tutorial, we learned how to create our Model, do the basic operations over it, and also create simple and beautiful routes to the same endpoint that do operations according to the HTTP methods and parameters. It opens a myriad of options to be explored!

In the next part we will learn how to build the page design to allow user interaction through the browser, using Leaf for templating and Skeleton to make page more beautiful. We also will modify our two GET routes to return pages.

Thank you for reading and see you in Part 3!


Author

Guilherme Teixeira

Guilherme Teixeira is a System Engineer Consultant at Avenue Code.


Building Accessible Web Applications

READ MORE

Create a Tunnel from Anypoint Platform to GCP Using an HA VPN

READ MORE

Create a Classic Tunnel from GCP to Anypoint Platform

READ MORE

How to Make an Android Splash Screen

READ MORE