Blog by Frank

Swift on Server

GET, JSON

import Vapor

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

func routes(_ app: Application) throws {
    // ...
    app.get("json", use: getJson)
    // ...
}

func getJson(req: Request) throws -> Person {
    return Person(name: "Frank", age: 20)
}
curl --location 'http://127.0.0.1:8080/json'

Colon

// curl --location 'http://127.0.0.1:8080/customers/12'
app.get("customers", ":customerId" , use: getCustomerId)

func getCustomerId(req: Request) async throws -> String {
    guard let customerId = req.parameters.get("customerId", as: Int.self) else {
        throw Abort(.badRequest)
    }
    return "\(customerId) is welcoming"
}

POST

import Foundation
import Vapor

struct Movie: Content {
    let title: String
    let year: Int
}

/*
curl --location 'http://127.0.0.1:8080/movies' \
--header 'Content-Type: application/json' \
--data '{
    "title": "Up",
    "year": 2020
}'
*/
app.post("movies") { req async throws in
    let movie = try req.content.decode(Movie.self)
    return movie
}

Query

import Foundation
import Vapor

struct HotelQuery: Content {
    let sort: String
    let search: String?
}

// curl --location 'http://127.0.0.1:8080/hotels?sort=desc'
app.get("hotels") { req async throws in
    let hotelQuery = try req.query.decode(HotelQuery.self)
    return hotelQuery
}

MVC Pattern Design


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

struct MoviesController: RouteCollection {
    func boot(routes: Vapor.RoutesBuilder) throws {
        // let app = routes.grouped("")
        // app.get { req async in ... }
        let movies = routes.grouped("movies")
        
        // /movies
        movies.get(use: index)
        
        // /movies/20
        movies.get(":moviesId", use: index)
    }
    
    func index(req: Request) async throws -> String {
        return "index"
    }
    
    func show(req: Request) async throws -> String {
        // 5xx case internalServerError
        guard let moviesIndex = req.parameters.get("moviesId") else { throw Abort(.internalServerError) }
        return "\(moviesIndex)"
    }   
}

import Vapor

struct Movie: Content {
    let title: String
    let year: Int
}

Middleware

Vapor.codes

Middleware is a logic chain between the client and a Vapor route handler. It allows you to perform operations on incoming requests before they get to the route handler and on outgoing responses before they go to the client.

Log Middleware

func routes(_ app: Application) throws {
    app.middleware.use(LogMiddleware())
}

import Vapor

struct LogMiddleware: AsyncMiddleware {
    func respond(to request: Vapor.Request, chainingTo next: Vapor.AsyncResponder) async throws -> Vapor.Response {
        print("LOG MIDDLEWARE")
        return try await next.respond(to: request)
    }
}

Authentication Middleware

/**
curl --location 'http://127.0.0.1:8080/members' \
--header 'Authorization: Bearer 123'
*/

import Vapor

struct AuthenticationMiddleware: AsyncMiddleware {
    func respond(to request: Vapor.Request, chainingTo next: Vapor.AsyncResponder) async throws -> Vapor.Response {
        
        // Headers: Authorization: Bearer 
        guard let auth = request.headers.bearerAuthorization else {
            throw Abort(.unauthorized)
        }
        print(auth.token)
        return try await next.respond(to: request)
    }
}

app.grouped(AuthenticationMiddleware()).group("members") { route in
    route.get { req async -> String in
        return "Members index"
    }
    route.get("hello") { req async -> String in
        return "Members Hello"
    }
}

Setting Fluent ORM

ORM: Object Relational Mapping

PostgreSQL as a Service Perfectly configured and optimized PostgreSQL databases ready in 2 minutes.

Migration

// configure.swift
app.databases.use(.postgres(hostname: "", username: "", password: "", database: ""), as: .psql)
app.migrations.add(CreateMoviesTableMigration())

struct CreateMoviesTableMigration: AsyncMigration {
    
    func prepare(on database: FluentKit.Database) async throws {
        // Create movies table
        try await database.schema("movies")
            .id()
            .field("title", .string, .required)
            .create()
    }
    
    func revert(on database: Database) async throws {
        // delete movies table
        try await database.schema("movies")
            .delete()
    }
    
}
vapor run migrate
vapor run migrate --revert

DB CRUD

// Create Movie
app.post("movies") { req async throws in
    let movie = try req.content.decode(Movie.self)
    try await movie.save(on: req.db)
    return movie
}

// Get All Movies
app.get("movies") { req async throws in
    try await Movie.query(on: req.db)
        .all()
}

// Get Specific Movie from id
// /movie/A7CD531E-B38B-4572-8FE4-DCA406A723EC
app.get("movie", ":id") { req async throws in
    guard let movie = try await Movie.find(req.parameters.get("id"), on: req.db) else {
        throw Abort(.badRequest)
    }
    return movie
}

// Get Specific Movie from id
// /movie/A7CD531E-B38B-4572-8FE4-DCA406A723EC
app.get("movie", ":id") { req async throws in
    guard let movie = try await Movie.find(req.parameters.get("id"), on: req.db) else {
        throw Abort(.badRequest)
    }
    return movie
}

app.delete("movie", ":id") { req async throws in
    guard let movie = try await Movie.find(req.parameters.get("id"), on: req.db) else {
        throw Abort(.badRequest)
    }
    try await movie.delete(on: req.db)
    return movie
}

// /movies Put
app.put("movies") { req async throws in
    let movie = try req.content.decode(Movie.self)
    guard let movieUpToDate = try await Movie.find(movie.id, on: req.db) else {
        throw Abort(.badRequest)
    }
    
    movieUpToDate.title = movie.title
    
    try await movieUpToDate.update(on: req.db)
    return movieUpToDate
}