Tutorial: How to write CRUD using Leaf
At the end of this tutorial you will Create, Read, Update and Delete (CRUD) on a User using Leaf! π
You can find the result of this tutorial on Github here
This tutorial is a natural follow-up of How to write Controllers. You can either go for that tutorial first and come back later or be a rebel, skip it and read on π
Index
1. Create a new project
2. Generate Xcode project
3. View Structure
4. The CREATE view
5. The READ view
6. The UPDATE view
7. The DELETE view
8. Where to go from here
1. Create a new project
We will use the outcome of the aforementioned tutorial as a template to create our new project:
vapor new projectName --template=vaporberlin/my-first-controller
2. Generate Xcode project
Before we generate an Xcode project we would have to change the package name within Package.swift:
// swift-tools-version:4.0
import PackageDescriptionlet package = Package(
name: "projectName", // changed
dependencies: [
// π§ A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0-rc"),
.package(url: "https://github.com/vapor/leaf.git", from: "3.0.0-rc"),
.package(url: "https://github.com/vapor/fluent-sqlite.git", from: "3.0.0-rc")
],
targets: [
.target(name: "App", dependencies: ["Vapor", "Leaf", "FluentSQLite"]),
.target(name: "Run", dependencies: ["App"]),
.testTarget(name: "AppTests", dependencies: ["App"]),
]
)
Now in the terminal at the root directory projectName/ execute:
vapor update -y
It may take a bit fetching the dependency, but when done you should have a project structure like this:
projectName/
βββ Package.swift
βββ Sources/
β βββ App/
β β βββ Controllers/
β β β βββ UserController.swift
β β βββ Models/
β β β βββ User.swift
β β βββ app.swift
β β βββ boot.swift
β β βββ configure.swift
β β βββ routes.swift
β βββ Run/
β βββ main.swift
βββ Tests/
βββ Resources/
β βββ Views/
β βββ userview.leaf
βββ Public/
βββ Dependencies/
βββ Products/
3. View Structure
Letβs create an own view for each operation. With userview.leaf within Resources/Views/ we already have kinda the CREATE and READ part. But letβs optimize and change the file for a better and cleaner overview. First step: rename the file to e.g. crud.leaf.
NOTE: Donβt forget to adjust your list function within your controller to use crud instead of userview when calling try req.view().render(β¦) βπ»π€
We will prepare our view to have a nice structure and we will make use of bootstrap classes for that. In Resources/Views/crud.leaf change it to:
<!DOCTYPE html>
<html>
<head>
<title>CRUD</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head><body class="container"> <h1 class="text-center"> CRUD </h1>
<div class="row">
<div class="col-xs-12 col-sm-3">
<h2>Create</h2>
</div> <div class="col-xs-12 col-sm-3">
<h2>Read</h2>
</div> <div class="col-xs-12 col-sm-3">
<h2>Update</h2>
</div> <div class="col-xs-12 col-sm-3">
<h2>Delete</h2>
</div> </div></body>
</html>
If you now cmd+r or run and fire up the /users route youβll see nice titles!
4. The CREATE view
Itβs super simple, we only need a form with an input field and a submit button:
...
<div class="col-xs-12 col-sm-3">
<h2>Create</h2>
<form method="POST" action="/users">
<div class="input-group">
<input type="text" name="username" class="form-control">
<span class="input-group-btn">
<input class="btn btn-success" type="submit" value="create" />
</span>
</div>
</form>
</div>
...
We are adding a form that is sending a POST request to the /users route with whatever we entered into the input field linked to the inputs name username.
This is what will arrive to our backend: username=whateverWeEntered
NOTE: No need to re-run your project when you change things in a leaf-file π
5. The READ view
The Read view is in terms of simplicity no different. Just add this:
...
<div class="col-xs-12 col-sm-3">
<h2>Read</h2>
#for(user in userlist) {
<div class="row">
<div class="col-xs-12">
<div class="form-group">
<input type="text" name="username" class="form-control" value="#(user.username)" disabled>
</div>
</div>
</div>
}
</div>
...
Since we are passing an array at the key userlist to our view from within our controller, we are able to simply loop on it. Letβs refresh our site. We can now create a new user and see a list of them!
6. The UPDATE view
Letβs get back to coding. We need a new route, in our routes.swift add this:
import Routing
import Vaporpublic func routes(_ router: Router) throws { let userController = UserController()
router.get("users", use: userController.list)
router.post("users", use: userController.create)
router.post("users", User.parameter, "update", use: userController.update)
}
We are going to conform our User to the Parameter protocol so that we are able to define a route like this. Itβs nothing fancy, the url that is matching this definition is β/users/23" so basically just the user Id. But instead of defining Int.parameter here and then trying to grab that number inside our controller and then use it to fetch the user with that id. We have a convenient way with defining User.parameter that will do all the hard work for us!
You will see it in the controller in a second. But first we have to conform our User to Parameter like so:
import FluentSQLite
import Vaporfinal class User: SQLiteModel {
var id: Int?
var username: String init(id: Int? = nil, username: String) {
self.id = id
self.username = username
}
}extension User: Content {}
extension User: Migration {}
extension User: Parameter {} // added
Now to our Controller/UserController.swift writing the update function:
import Vaporfinal class UserController { func list(_ req: Request) throws -> Future<View>
...
} func create(_ req: Request) throws -> Future<Response> {
...
} func update(_ req: Request) throws -> Future<Response> {
return try req.parameters.next(User.self).flatMap { user in
return try req.content.decode(UserForm.self).flatMap { userForm in
user.username = userForm.username
return user.save(on: req).map { _ in
return req.redirect(to: "/users")
}
}
}
}
}struct UserForm: Content {
var username: String
}
Okay we have a lot going on here letβs break it down.
So first with req.paremeters.next(User.self) weβll get our user object fetched from our database for us with the id that was given at the url e.g: βusers/23β.
Now this will return a Future<User> and in order to access a value inside a Future we will always have to use one of the map functions. The question is which one? Letβs recap. When we call map or flatMap itβs always on a Future.
We choose map if the body of the call returns a non-future value.
someFuture.map { return value }
And we call flatMap if the body does return a future value.
someFuture.flatMap { return Future<value> }
So secondly we have req.content.decode(UserForm.self) in which we make use of codable. If we have a struct or class that conforms to content and it has all properties that we receive from a Form (could also be JSON) we can easily say to what we want to decode and that will give us an instance of that object.
Finally we are overriding the username of the user with the username of the userForm, save the user and return a redirect. Thatβs it. Letβs go and add the corresponding Form to crud.leaf:
...
<div class="col-xs-12 col-sm-3">
<h2>Read</h2>
#for(user in userlist) {
<form method="POST" action="/users/#(user.id)/update">
<div class="input-group form-group">
<input type="text" name="username" class="form-control" value="#(user.username)">
<span class="input-group-btn">
<input class="btn btn-primary" type="submit" value="update" />
</span>
</div>
</form>
}
</div>
...
We want to be able to update every user we have, so we loop on the userlist and create a form with method POST to the route users/#(user.id)/update for each of them. We will have for each user a filled out input field with his username and if we change and submit it, we will immediately see the update. We donβt need to rerun for changes in our view, but we added a new function and route β so letβs cmd+r or run our project, refresh our site and try updating a username! I really like how simple it is!
7. The DELETE view
This one is simple too. In our Controllers/UserController.swift add:
final class UserController { func list(_ req: Request) throws -> Future<View> {
...
} func create(_ req: Request) throws -> Future<Response> {
...
} func update(_ req: Request) throws -> Future<Response> {
...
} func delete(_ req: Request) throws -> Future<Response> {
return try req.parameters.next(User.self).flatMap { user in
return user.delete(on: req).map { _ in
return req.redirect(to: "/users")
}
}
}
}
I think (hope π€π») this code is self explaining, too. Letβs define our last route:
import Routing
import Vaporpublic func routes(_ router: Router) throws {
let userController = UserController()
router.get("users", use: userController.list)
router.post("users", use: userController.create)
router.post("users", User.parameter, "update", use: userController.update)
router.post("users", User.parameter, "delete", use: userController.delete)
}
And finally in our crud.leaf the laaast piece of code:
<div class="col-xs-12 col-sm-3">
<h2>Delete</h2>
#for(user in userlist) {
<form method="POST" action="users/#(user.id)/delete">
<div class="form-group input-group">
<input type="text" name="username" class="form-control" value="#(user.username)" disabled>
<span class="input-group-btn">
<input class="btn btn-danger" type="submit" value="delete" />
</span>
</div>
</form>
}
</div>
Our final cmd+r or run and refresh of our site and thatβs it! You successfully implemented a CRUD API with Leaf π!
8. Where to go from here
You can find a list of all tutorials with example projects on Github here:
ππ» https://github.com/vaporberlin/vaporschool