Server side Swift for Laravel developers part 3


Views

Next up we are going to add some views.

Unfortunately, Xcode does not render Leaf-templates correctly. You could manually set the type to HTML, but this will revert every time you build. Instead, I went back to good old Sublime for editing templates. First install Leaf - Packages - Package Control

Open Resources/Views/base.leaf and add Bootstrap 4 in the header. We cannot hurt our eyes with unsettled content :-)

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">

As you can see Leaf works very similar to Laravel and Blade.

Create Resources/Views/index.leaf and add a simple index-page

#extend("base")

#export("title") { Stations }

#export("content") {

    <table class="table">
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Country</th>
            <th>Stream</th>
        </tr>
        #loop(stations, "station") {
            <tr>
                <td><a href="/station/#(station.id)">#(station.id)</a></td>
                <td>#(station.name)</td>
                <td>#(station.country)</td>
                <td>#(station.stream)</td>
            </tr>
        }
    </table>

    <a href="/create" class="btn btn-default">Add a station</a>
}

Instead of section, Leaf is using export as markup.

Create Resources/Views/edit.leaf for creating records


#extend("base")

#export("title") { Stations }

#export("content") {

    <form class="form" action="/station/#(station.id)" method="post">
        <div class="form-group">
            <label for="name">Name</label>
            <input class="form-control" type="text" name="name" value="#(station.name)">
        </div>
        <div class="form-group">
            <label for="description">Description</label>
            <textarea class="form-control" name="description" id="description" cols="30" rows="10">#(station.description)</textarea>
        </div>
        <div class="form-group">
            <label for="country">Country</label>
            <input class="form-control" type="text" name="country" value="#(station.country)">
        </div>
        <div class="form-group">
            <label for="stream">Stream URL</label>
            <input class="form-control" type="text" name="stream" value="#(station.stream)">
        </div>

        <button type="submit" class="btn btn-primary">Save</button>
        <a href="/" class="btn btn-default">Back</a>
    </form>

}

Connect the dots

Like Laravel, you can create controllers and RESTful resources. But for this tutorial, we will just use the Routes.swift and doing all operations directly.

First make an index view, should not be any surprises if you are used to Laravel.


        builder.get { req in
            
            // Get all stations
            let stations = try Station.makeQuery().all()
            
            
            return try self.view.make("index", [
                "stations": stations
            ])
        }
    
        builder.get("station", ":id") { req in
            
            // Make sure the request contains an id
            guard let stationId = req.parameters["id"]?.int else {
                throw Abort.badRequest
            }

            let station = try Station.makeQuery().find(stationId)
            
            return try self.view.make("edit", [
                "station": station
            ])
        }
        
        builder.get("station", "create") { req in
            return try self.view.make("edit")
        }
        
        builder.post("station") { req in
            guard let form = req.formURLEncoded else {
                throw Abort.badRequest
            }
            
            let station = try Station(node: form)
            try station.save()
            
            return Response(redirect: "/")
        }

Finally, we are adding a route for updating an existing record. As mentioned earlier, Swift is strict and just refresh the model would require a lot of checks. By going via Vapor’s Node package and create a new model and assigning it back to the original record was the easiest way I found. If you have better solutions, feel free letting me know.


 builder.post("station", ":id") { req in
            
            // Make sure it's a form posted
            guard let form = req.formURLEncoded else {
                throw Abort.badRequest
            }
            
            // Make sure the request contains an id
            guard let stationId = req.parameters["id"]?.int else {
                throw Abort.badRequest
            }
            
            guard let station = try Station.makeQuery().find(stationId) else {
                throw Abort.notFound
            }
            
            // Use Vapor's node functions to create a new entity
            let newStation = try Station(node: form)
            
            // Assign the new values back to the old
            station.country = newStation.country
            station.name = newStation.name
            station.description = newStation.description
            
            // ...and save
            try station.save()
            
            return Response(redirect: "/")
        }

Debugging in Xcode

A very nice feature using Xcode is that you get all the debugging features that you would expect from an IDE.

Try putting a breakpoint on the route for getting a station, and you can inspect the results.

Step by step

Conclusions

Overall Vapor was a delightful surprise that it feels very Laravel-ish, I am sure the developers of Vapor have looked a lot a Laravel. For me using the same language for the backend and your apps will be a deal maker for many developers, especially if you are using Vapor as a RESTful backend. Vapor feels super snappy, and the performance is incredible, and the memory footprint is very low.

Swift in its nature of being strictly type hinted makes some operations like models to have a lot of boiler plate. This could be resolved by a code generator like GitHub - krzysztofzablocki/Sourcery: Meta-programming for Swift, stop writing boilerplate code. Unfortunately, there is no such tool creating Vapor models here yet.

All of your edits also demands that everything is re-compiled. Apple has made tremendous efforts to make this more speedy. Obviously, it’s not so easy as save your changes and hit reload in the browser.

Will I change? If I am about to build a JSON backend for an iOS-app, I will most likely look into Vapor. In some parts, I could even reuse the same code between the iOS-app and the backend. For me building a SAAS with a lot of party at the front end I would for sure stay with Laravel and maybe use Laravel Spark, because of the more mature tooling like components, seedings, Mix, Vue.js and so forth.

Download complete source