Swiftpack.co - Package - swift-glide/glide

Glide

Swift 5.2 GitHub release CI

A Swift micro-framework for server-side development. Inspired by Express, Sinatra, Flask, etc.

⚠️ This is a work in progress and should not be used in production.

Usage

Getting Started

Start off by creating a Swift package for your own app:

mkdir APP_NAME
cd APP_NAME
swift package init --type executable --name APP_NAME
git init

In your Package.swift file, add the following line in dependencies: [...]:

.package(url: "https://github.com/kaishin/glide", .branch("master"))

And in the targets section, add Glide as a depdency to your main target:

targets: [
    .target(
      name: APP_NAME,
      dependencies: ["Glide"]
    )

Then, in the main.swift of your server-side app, add the following:

// 1. Import the framework
import Glide

// 2. Instantiate the app
let app = Application()

// 3. Add a route.
app.get("/hello") { _, response in
  response.text("Hello, world!")
}

// 4. Start listening on a given port
app.listen(1337)

Xcode

Double-click your Package.swift file so that it opens in Xcode. Wait for the dependencies to be automatically installed then run the project.

Command Line / Linux

If you are not using Xcode, run these commands in the terminal:

swift package update
swift build
swift run

Once the project is running either via Xcode or the Swift CLI, run the following in your terminal:

curl "http://localhost:1337/hello"
# -> "Hello, world!"

Middleware

Glide uses a highly flexible middleware architecture. Each request will go through a chain of middleware functions matching its route and triggering side-effects such as reading from a database or fetching data from a remote server.

A middleware function receives a request and a response and return a future (of type EventLoopFuture, aliased to Future in Glide) wrapping an output. It can modify both the request and the response, which are reference types. When an error occurs in the body of the middleware function, it can be thrown and left for other error handlers to catch. More on error handling later.

The middleware signature is the following:

typealias Middleware = (Request, Response) throws -> EventLoopFuture<MiddlewareOutput>

enum MiddlewareOutput {
  case next
  case text(String)
  case data(Data)
  case file(String)
  
  static func json<T: Encodable>(_ model: T) -> Self { ... }
}

Any function that has the same signature can be used as middleware. Here is a function that adds a response header to any response that goes through it.

func waldoMiddleware(_ request: Request, _ response: Response) throws -> EventLoopFuture<MiddlewareOutput> {
  response["My-Header"] = "waldo"
  return request.next()
}

To register the middleware, call the use() method on Application when configuring your app:

let app = Application()
app.use(waldoMiddleware)

For convenience, Glide introduces a number of middleware generators that handle the most common use cases such as routing, CORS, etc. More on these in dedicated sections.

Routing

Routing is the process of matching the path of a request to a specific middleware. Since it's a common operation, Glide provides dedicated middleware generators such as get(), post(), patch(), etc.

For example, if you want your app to return a list of todos when the user visits the /todos URL, you can do the following:

app.get("/todos") { request, response in
  let todos = ... // Get a list of todos from a database, file, remote server, etc.
  
  return response.json(todos)
}

Parameters & Queries

In the real world, requests will likely have a path or query parameter in them. To tackle that, Glide uses custom string interpolation to create path expressions used for route matching.

To illustrate, let's say that we want to return a specific todo to the end user. We know that the id property of the todo is an Int. Here's how we can handle that:

app.get("/todos/\("id", as: Int.self)") { request, response in
  /* We specify the type of the variable to help the 
  compiler pick the right dynamic memeber lookup method. */
  let id: Int = request.pathParameters.id 
  
  if let todo = findTodo(id) {
    return .json(todo)
  } else {
    throw CustomError.todoNotFound  
  }
}

If we are interested in the parameter as a string, the above can be shortened to get("/todos/\("id")). Query parameters work in a similar fashion:

app.get("/todos") { request, response in
  let sortOrder = request.queryParameters.sortOrder ?? "DESC"
  let sortedTodos = ... // Get a list of todos with the sort order.
  
  return .json(sortedTodos)
}

and in the shell:

curl "http://localhost:1337/todos?sortOrder=ASC"

PS: Path and query parameters might change in the future based on usage feedback.

Wildcards

Sometimes a route has to match any request path, storing nameless path components for later perusal. For those cases, the wildcard custom interpolation in PathExpression comes in handy:

app.get(/todos/\(wildcard: .all))
/* This will match all the following:
  /todos/foo/bar
  /todos
  /todos/23/baz/qux
*/

The path components after the wildcard are collected in the order they appear as strings in the request.pathParameters.wildcards array. Similary, \(wildcard: .one) can be used to mark only one path of the segment as an nameless path parameter.

Error Handling

WIP

Static Files

WIP

Roadmap

  • Add error middleware.
  • Add support for path and query parameters.
  • Add support for static files. Streaming is not supported yet.
  • Add support for templating packages.
  • Add support for sessions & cookies.
  • Add support for uploads.
  • Add support for redirects.
  • Add support for state storage.
  • Add support for Web forms.

Github

link
Stars: 10

Used By

Total: 0

Releases

Better Error Handling -

This release makes error handling inside middleware a lot more straightforward. It also adds support for automatic response serialization for asynchronous NIO errors.

Fix Dependency URL -

Fix File Middleware and Add Basic Templating Support -

Asynchronous Middleware Output -

The vast majority of operations performed in a server-side app are asynchronous: database connections, file reads, network requests, etc. As such the middleware API introduced so far was categorically flawed. This releases fixes that by introducing EventLoopFuture based middleware that can ensure good interop with other existing Swift server-side software.

Update package syntax to 5.2 -

Type-safe Middleware -

This release makes working with custom middleware a little less error-prone. Thanks to @jnutting for the feedback and pitch.

Prior to this, it was left to the developer to remember to either call next() or finalize the response. This release adds a requirement for middleware to return a result, which will put the compiler to work and help avoid human-error.

Static file Middleware -

Now you can use the staticFileHandler in your app to serve static files.

app.use(staticFileHandler())

By default, it will look for files in the /Public folder relative to the working directory. In Xcode, this has to be set up in the scheme manually.

To override the static file directory:

app.use(staticFileHandler("/Static"))

Path & Query Parameters -

Handle POST requests -