Slide 1

Slide 1 text

Vapor 101 !

Slide 2

Slide 2 text

Steffen D. Sommer Lead Vapor Developer @Nodes • iOS: 3 years • Vapor: 1,5 months • Swift = ! • Twitter: @steffendsommer

Slide 3

Slide 3 text

Nodes ❤ Vapor • Mar 2016: Nodes invests in Vapor • Sep 2016: First customer project in Vapor at Nodes • 2017+: All new projects in Vapor

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Project quickstart

Slide 6

Slide 6 text

Installing the Toolbox (Remember to install Xcode) curl -sL check.vapor.sh | bash curl -sL toolbox.vapor.sh | bash vapor --help

Slide 7

Slide 7 text

Creating the project vapor new HelloWorld cd HelloWorld vapor xcode -y

Slide 8

Slide 8 text

Core concepts

Slide 9

Slide 9 text

Droplets

Slide 10

Slide 10 text

One type to rule them all: Droplet • Server/Client • Routing • Middlewares • Providers • Configuration • .. and a lot more

Slide 11

Slide 11 text

A simple Droplet instance import Vapor let drop = Droplet() drop.run()

Slide 12

Slide 12 text

Models

Slide 13

Slide 13 text

The Model protocol • NodeInitializable • NodeRepresentable • Preparation

Slide 14

Slide 14 text

.. but what is Node? public enum Node { case null case bool(Bool) case number(Number) case string(String) case array([Node]) case object([String: Node]) case bytes([UInt8]) }

Slide 15

Slide 15 text

A simple Post model final class Post: Model { var id: Node? var title: String var content: String? }

Slide 16

Slide 16 text

The NodeInitializable protocol init(node: Node, in context: Context) throws { id = try node.extract("id") title = try node.extract("title") content = node["content"]?.string }

Slide 17

Slide 17 text

The NodeRepresentable protocol func makeNode(context: Context) throws -> Node { return try Node(node: [ "id": id, "title": title, "content": content ]) }

Slide 18

Slide 18 text

The Preparation protocol static func prepare(_ database: Database) throws { try database.create(entity) { $0.id() $0.string("title") $0.string("content") } } static func revert(_ database: Database) throws { try database.delete(entity) }

Slide 19

Slide 19 text

Fluent and the Entity protocol • Saving • Deleting • Querying • .. and more

Slide 20

Slide 20 text

Controllers

Slide 21

Slide 21 text

Index func index(request: Request) throws -> ResponseRepresentable { return try Post.all().makeJSON() }

Slide 22

Slide 22 text

Create func create(request: Request) throws -> ResponseRepresentable { guard let json = request.json else { throw Abort.badRequest } var post = try Post(node: json) try post.save() return post }

Slide 23

Slide 23 text

Show func show(request: Request, post: Post) throws -> ResponseRepresentable { return post }

Slide 24

Slide 24 text

Delete func delete(request: Request, post: Post) throws -> ResponseRepresentable { try post.delete() return JSON([:]) }

Slide 25

Slide 25 text

Update func update(request: Request, post: Post) throws -> ResponseRepresentable { guard let json = request.json else { throw Abort.badRequest } let new = try Post(node: json) var post = post post.content = new.content post.title = new.title try post.save() return post }

Slide 26

Slide 26 text

The ResourceRepresentable protocol func makeResource() -> Resource { return Resource( index: index, store: create, show: show, modify: update, destroy: delete ) }

Slide 27

Slide 27 text

Routes

Slide 28

Slide 28 text

Routes with ResourceRepresentable drop.resource("posts", PostController())

Slide 29

Slide 29 text

Routes without ResourceRepresentable drop.get("posts", handler: PostController().index) drop.get("posts", Post.self, handler: PostController().show) drop.post("posts", handler: PostController().create)

Slide 30

Slide 30 text

Routes for websites drop.get("posts/view") { request in return try drop.view.make("posts", ["posts": try Post.all().makeNode()]) }

Slide 31

Slide 31 text

Route groups drop.group("v1") { v1 in v1.get("posts", handler: PostController().index) }

Slide 32

Slide 32 text

Views

Slide 33

Slide 33 text

The Leaf template language Posts
    #loop(posts, "post") { #(post.title)! }

Slide 34

Slide 34 text

Providers

Slide 35

Slide 35 text

A simple way of adding functionality to your project • Add the dependency to Package.swift • Import the module • Add the provider to your Droplet instance • * Some providers requires adding a config file

Slide 36

Slide 36 text

1/3: Adding persistence using MySQL Add dependency to Package.swift .Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 1)

Slide 37

Slide 37 text

2/3: Adding persistence using MySQL Import and add it to your Droplet instance import VaporMySQL let drop = Droplet() try drop.addProvider(VaporMySQL.Provider.self)

Slide 38

Slide 38 text

3/3: Adding persistence using MySQL Add Config/mysql.json { "host": "127.0.0.1", "user": "root", "password": "", "database": "mydb" }

Slide 39

Slide 39 text

Middlewares

Slide 40

Slide 40 text

Client <-> Middleware <-> Droplet

Slide 41

Slide 41 text

Examples of middlewares • Authentication • Error reporting/handling • Data transformation • Header enforcing/manipulation • .. etc.

Slide 42

Slide 42 text

Adding a middleware import MyErrorMiddleware let drop = Droplet() drop.middleware.append(MyErrorMiddleware())

Slide 43

Slide 43 text

1,5 months in: Gotchas

Slide 44

Slide 44 text

1: Xcode project and tests • To run tests on Linux you need to have multiple targets • See https://vapor.github.io/documentation/ testing/modules.html for guide • Use https://github.com/nodes-vapor/template as template for automatic setup

Slide 45

Slide 45 text

2a: Linux tests class AppLogicTests: XCTestCase { static var allTests = [ ("testExample", testExample) ] func testExample() throws { // Just a dummy test to satisfy GitLab CI. XCTAssert(true) } }

Slide 46

Slide 46 text

2b: Linux tests XCTMain([ testCase(AppLogicTests.allTests), ]) See https://github.com/BrettRToomey/toolbox/tree/ test-fix-command if you feel #yolo

Slide 47

Slide 47 text

3: Database transactions func deletePostTransaction(post: Post) throws { guard let driver = driver else { throw Error.noMySQLDriver } guard let postId = post.id else { throw Error.postIdNotFound } let connection = try driver.database.makeConnection() try connection.transaction { // Delete post. try connection.execute( "UPDATE posts SET deleted_at = ? where id = ?", [Date().mysql, postId] ) try connection.execute( "UPDATE posttags SET deleted_at = ? WHERE post_id = ?", [Date().mysql, postId] ) } }

Slide 48

Slide 48 text

4: The exists variable • Required by Fluent to keep track of if a model should be updated or created on save() • When querying through Fluent, the returned models will have the property set • When doing raw queries and creating models from Node's, remember to set the property

Slide 49

Slide 49 text

5: Migrations final class PostAddAuthorColumn: Preparation { static func prepare(_ database: Database) throws { try database.driver.raw(“ALTER TABLE posts ADD COLUMN author VARCHAR(255);”) } static func revert(_ database: Database) throws { try database.driver.raw(“ALTER TABLE posts DROP COLUMN author;”) } } Remember to append to the Droplet instance: drop.preparations.append(PostAddAuthorColumn.self)

Slide 50

Slide 50 text

6a: The type infer madness func makeNode(context: Context) throws -> Node { return try Node(node: [ "id": id, "title": title, "content": content, ]) }

Slide 51

Slide 51 text

6b: The type infer madness func makeNode(context: Context) throws -> Node { return try Node(node: [ "id": id, "title": title, "content": content, "createdAt": createdAt, "deletedAt": deletedAt, "updatedAt": updatedAt, "tags": tags, "authorId": authorId, "totalComments": totalComments, "type": type, "authorsOnly": authorsOnly, "relatedPost": relatedPost, "coAuthorId": coAuthorId, "summary": summary, "subtitle": subtitle ]) }

Slide 52

Slide 52 text

6c: The type infer madness func makeNode(context: Context) throws -> Node { var node: [String: NodeRepresentable] = [:] node["id"] = id node["title"] = title node["content"] = content node["createdAt"] = createdAt node["deletedAt"] = deletedAt node["updatedAt"] = updatedAt node["tags"] = tags node["authorId"] = authorId node["totalComments"] = totalComments node["type"] = type node["authorsOnly"] = authorsOnly node["relatedPost"] = relatedPost node["coAuthorId"] = coAuthorId node["summary"] = summary node["subtitle"] = subtitle return try Node(node: node) }

Slide 53

Slide 53 text

7: macOS Foundation != Linux Foundation • Be careful of what you are using from Foundation • Keep an eye on https://github.com/apple/swift- corelibs-foundation for status on the port of Foundation

Slide 54

Slide 54 text

Useful resources • Vapor Docs: http://docs.vapor.codes • The Vapor Source code: https://github.com/vapor • #help on Slack (qutheory.slack.com) • Vapor @Nodes: https://github.com/nodes-vapor

Slide 55

Slide 55 text

! Questions?