How to use Vapor

Scott
9 min readNov 28, 2022

--

For sofware engineers.

1. Download brew if you don’t have it.

2. Install vapor with the following command.

brew install vapor

3. Then create a vapor project:

cd <to your desired project location>
vapor new <project name>

4. Open your Vapor project by double clicking package.swift

5. Set your scheme’s target as My Mac

6. After your dependencies have been loaded,

tap play or “⌘r”.

7. You might notice a debug readout:

To make that go away, Click on Sheme:

Then Tap run.
Choose Options
Tick working directory
Then get the path to your project

8. Get url

In the console you might notice the console log. That tells you the url it is running on. You only need to throw that in a browser.

Throw it in a browser
You should get this. ^

9. The Routes file is where the url flows through.

10. Nest routes

You can “nest” url routes, by passing a list of string literals as parameter arguments.

For example:

localhost:8080/hello/my/friend

Would be done like so:

app.get("hello", "my", "friend") { req -> String in
"Hello my Friend!"
}

11. You can use route parameters, or treat them like variables like so:

localhost:8080/hello/my/friend/<Friend name>

ie:
localhost:8080/hello/my/friend/Jerry

Would be done like so:

app.get("hello", "my", "friend", ":friendName") { req -> String in
guard let name = req.parameters.get("friendName") else {
throw Abort(.badRequest)
}
"Hello my friend, \(name)!"
}

▶️ run the app.

12. Anything routes:

// Anything routes
// /foo/bar/baz
// /foo/zxy/baz
// /foo/qwe/baz

app.get("foo", "*", "baz") { req in
"FOO BAZ"
}

▶️ run the app.

13.Catchall routes:

// catchall routes
// /foo/bar
// /foo/bar/baz

app.get("foo", "**") { req in
"FOO"
}

▶️ run the app.

14. Query strings:

// /search?keyword=toys&page=12

app.get("search") { req -> String in
guard let keyword: String = req.query["keyword"],
let page: String = req.query["page"]
else {
throw Abort(.badRequest)
}
return "Keyword = \(keyword) and page = \(page)"
}

▶️ run the app.

15. Route Groups: (helpful for organizing your code).

// /users/12
// /users
// POST /users

let users = app.grouped("users")

// /users
users.get { req in
"/users"
}

// users/23
users.get(":userId") { req -> String in
guard let userId = req.parameters.get("userId") else {
throw Abort(.badRequest)
}
return "userID = \(userId)"
}

users.post { req in
"POST"
}

▶️ run the app.

16. Query Strings:

// /search?keyword=toys&page=12

app.get("search") { request in
let keyword: String? = request.query["keyword"]
let page: String? = request.query["page"]
}

▶️ run the app.

17. Route Groups:

Lets you capture a reusable path component or “group”

let users = app.grouped("users")

// /users
users.get { request in
return "/users"
}

users.get(":userId") { req -> String in
guard let userId = req.parameters.get("userId") else {
throw Abort(.badRequest)
}
return "userId = \(userId)"
}

users.post { req -> in
return "POST"
}

18. Controllers, it would suck to have to put all your project’s code in one place, in one file.


func routes(_ app: Application) throws {
try app.register(collection: UserController())
}

struct UserController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let users = routes.grouped("users")
users.get(use: index)
users.post(use: create)
}

func index(request: Request) throws -> String {
"Index"
}

func create(request: Request) throws -> String {
"CReate"
}
}

▶️ run the app.

19. Return JSON,

as long as your response conforms to ResponseEncodable , which is basically JSON’s requirements, then you are good to return it.

   
func routes(_ app: Application) throws {
try app.register(collection: APIController())
}

// in another file
import Vapor

struct APIController: RouteCollection {

func boot(routes: Vapor.RoutesBuilder) throws {
let api = routes.grouped("api")
api.get("users", use: getUsers)
}

func getUsers(req: Request) throws -> [[String: String]] {
let users = [["name": "Alex"], ["name": "Mary"]]
return users
}
}

▶️ run the app.

20. Getting JSON by returning a Response .

You can swap out the above function with the one below and more structured JSON, in cases where you can’t send Any as a type.

Try this instead:

    func getUsers(req: Request) throws -> Response {
let users = [["name": "Alex", "age": 32], ["name": "Mary", "age": 56]]
let data = try JSONSerialization.data(withJSONObject: users, options: .prettyPrinted)
return Response(status: .ok, body: Response.Body(data: data))
}

21. Suppose we want to return a custom type

make sure it conforms to Content which is Codable, RequestDecodable, ResponseEncodable, AsyncRequestDecodable, AsyncResponseEncodable .

struct User: Content {
var name: String
var age: Int
}

struct APIController: RouteCollection {

func boot(routes: Vapor.RoutesBuilder) throws {
let api = routes.grouped("api")
api.get("users", use: getUsers)
}

func getUsers(req: Request) throws -> [User] {
[User(name: "Mary", age: 32)]
}
}

This works for nested custom types as well.

import Vapor

struct User: Content {
var name: String
var age: Int
var address: Address
}

struct Address: Content {
var street: String
var city: String
var state: String
var zip: Int
}

struct APIController: RouteCollection {

func boot(routes: Vapor.RoutesBuilder) throws {
let api = routes.grouped("api")
api.get("users", use: getUsers)
}

func getUsers(req: Request) throws -> [User] {
[
User(
name: "Mary",
age: 32,
address: Address(
street: "Lettoonski",
city: "San Francisco",
state: "CA",
zip: 04034
)
)
]
}
}

22.Postman

If you aren’t too crazy about the plain white background when you are viewing your JSON you can download postman. It also helps with POST requests which are our next step. Postman provides a pretty user interface for interacting with Requests and Responses .

Click sign in or create an account. or skip and go to the app.

It should show you something like this.

23. Make a post request, you have to call the post method.


struct APIController: RouteCollection {

func boot(routes: Vapor.RoutesBuilder) throws {
let api = routes.grouped("api")
api.post("users", use: create)
}

func create(req: Request) throws -> HTTPStatus {
let user = try req.content.decode(User.self)
print(user)
return .ok
}
}

Now lets put in the following for the body:

{
"name": "Christopher Robin",
"age": 24
}

Lets send it without the address to see what happens.

Note when the content type is text: Content-Type: text/plain

{
"error": true,
"reason": "Value of type 'CodingKeys' required for key ''."
}

When the content type is Content-Type: application/json we get a more specific error.

{
"error": true,
"reason": "Value required for key 'address'."
}

I found that out by printing the request and pasting it in Text Compare.

If you set the address to nil, then you will find the below.

200 OK is there in the top right!

24. Here is one way to return response with a body.

25. Next lets install POSTGRESQL . You can download it here.

Lets download the postgress app
Follow the installation guide and you will see something like this when you open it.

Click Initialize, and it will present some boilerplate databases, one will have your username.

Double click on your username and it will open a postgreSQL terminal.

Lets type:

CREATE DATABASE mydatabase;
Now we see the database we created.

CREATE DATABASE doesn’t need to be capitalized, but it is good form.

26. Lets use Fluent !

An ORM (Object relation mapper) “driver” which will allow us to make database commands directly from swift.

For example:

Movies.all() // to get all the movies from the SQl database. 

Creation, deletion, update, everything you can do with SQL, you can do with the classes that Fluent provides to you. You don’t have to write SQL by hand, only dealing with the classes. Each table is represented by the name of the class, and each property is represented by the property.

27. Adding Fluent to a new project (section 6. 27)

Open terminal

cd to your target directory.

vapor new <name of your project>
When you

You can double click on the Package.swift file to open the project.

Then choose a database, this tutorial is using postgresql.

Then choose if you want to use leaf.

Then open the new project’s folder.

open Package.swift

You should see Fluent included, and its driver:

Make sure you set the scheme to my mac.

We also need to set the working directory. Click edit scheme.

Tick the box next to working Directory:

Select the folder, and make sure you set your working directory to the same as the project root directory.

In our case, the project is on the desktop, so we click choose.

28. Go to this to install it to an existing package.

29. Download Postico,

which is a simple user interface which will show you contents of your table. If it doesn’t let you drag it to applications you can double click it.

30. Resources:

Official Postgres Website: https://www.postgresql.org/

Postgres.app: https://postgresapp.com/downloads.html

POSTMAN: https://www.postman.com/downloads/

31. Postgres

Open Postgres

Open the database terminal by clicking any of the databases.

Part 2.

--

--