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
}