Server side Swift for Laravel developers part 2


In this short example, we are going to build a simple CRUD-app of radio-stations that are used in my tvOS-app Radio Player.

If you have never programmed in Swift, there are some thing you need to know. Apple has designed Swift to be a static type-safe language with static dispatch. There cannot be any loose ends at compile time; everything must add up. Why is this? Apple’s motifs for this is performance, low memory usage, and stability; bugs should be caught at compilation and not in runtime. By defaulting to making structs and immutable variables is one way for example.

Swift 3 has all the bells and whistles you would expect from modern languages with influences from Rust, Haskell, Ruby, Python, C#. With Swift 2.0 Apple also introduced Protocol-Oriented Programming ) in replacement of Object Oriented programming. There are a lot of resources that can explain this better than I can.

The nature of the web is very stringy and combine this with Swift’s strict typing, and you have a bit of a problem. Vapor tries to resolve this with a package called Node to help and overcome this issue. Swift also misses multi line strings, which is planned to Swift 4. There are some quirks that you are not used to when doing PHP.

Some operation demands a lot more of boiler plate code than you are maybe used to. Swift features a very limited runtime and has no meta-programming features, which leads our projects to contain more boilerplate code. One solution to this could be the tool like Sourcery. Unfortunately there is no such tool yet. I think this is only beginning.

Create a model

Create a file in Model/Station.swift and create a class that extends Model like this

import Vapor
import MySQLProvider

final class Station: Model {
    
    // The storage property is there to allow Fluent to store extra 
    // information on your model--things like the model's database id.
    let storage = Storage()
    
    var id: Node?
    var name: String
    var description: String
    var country: String
    var stream: String
    
    // Just static variables holding the names to prevent typos
    static let nameKey = "name"
    static let descriptionKey = "description"
    static let countryKey = "country"
    static let streamKey = "stream"
    
    init(name: String, description: String, country: String, stream: String) {
        self.id = nil
        self.name = name
        self.description = description
        self.country = country
        self.stream = stream
    }
    
    // The Row struct represents a database row. Your models should 
    // be able to parse from and serialize to database rows. 
    // Here's the code for parsing the Stations from the database.

    init(row: Row) throws {
        id = try row.get(Station.idKey)
        name = try row.get(Station.nameKey)
        description = try row.get(Station.descriptionKey)
        country = try row.get(Station.countryKey)
        stream = try row.get(Station.streamKey)
    }
    
    // Init model from a Node-structure
    init(node: Node) throws {
        name = try node.get(Station.nameKey)
        description = try node.get(Station.descriptionKey)
        country = try node.get(Station.countryKey)
        stream = try node.get(Station.streamKey)

    }
    
    func makeRow() throws -> Row {
        var row = Row()
        try row.set(Station.nameKey, name)
        try row.set(Station.descriptionKey, description)
        try row.set(Station.countryKey, country)
        try row.set(Station.streamKey, stream)
        
        return row
    }
}

We need to show Vapor how to save it back into the database. We add a method called makeNode() with instructions. Just to make it more clear we create an extension that conforms to the NodePrespesentable protocol. Extensions can add new functionality to an existing class, structure, enumeration, or protocol type. Interfaces vs Inheritance in Swift

extension Station: NodeRepresentable {
    func makeNode(in context: Context?) throws -> Node {
        var node = Node(context)
        try node.set(Station.idKey, id?.int)
        try node.set(Station.nameKey, name)
        try node.set(Station.descriptionKey, description)
        try node.set(Station.countryKey, country)
        try node.set(Station.streamKey, stream)
        
        return node
    }
}

The cool thing is that protocols can be adopted by classes, structs, and enums. Base classes and inheritance are restricted to class types. You can decorate with default behaviors from multiple protocols. Unlike multiple inheritances of classes which some programming languages support, protocol extensions do not introduce any additional state.

Finally, we have to make a migration, called preparation in Vapor for the model.

extension Station: Preparation {
    static func prepare(_ database: Database) throws {
        try database.create(self) { builder in
            builder.id()
            builder.string(Station.nameKey)
            builder.string(Station.descriptionKey)
            builder.string(Station.countryKey)
            builder.string(Station.streamKey)
        }
    }
    
    static func revert(_ database: Database) throws {
        try database.delete(self)
    }
}

Next, we are going to add MySQL and Leaf-providers to the droplet, as you would do in Laravel

Source/App/Config+Setup.swift

import LeafProvider
import MySQLProvider

extension Config {
    public func setup() throws {
        // allow fuzzy conversions for these types
        // (add your own types here)
        Node.fuzzy = [JSON.self, Node.self]

        try setupProviders()
    }
    
    /// Configure providers
    private func setupProviders() throws {
        try addProvider(LeafProvider.Provider.self)
        try addProvider(MySQLProvider.Provider.self)
        
        // Run migrations aka preparations
        preparations.append(Station.self)
    }
}

Open Sources/App/Routes.swift and add a route.

builder.get { req in
     return try Station.makeQuery().all().makeJSON()
}



Manually add some records in the database. Vapor does not have any handy seeding functions.

Add record

Press the Run-button in Xcode

Press run


Visit http://localhost:8080 to see the JSON output —