肘子的 Swift 记事本

Installation and Configuration of Vapor4

Published on

Get weekly handpicked updates on Swift and SwiftUI!

This article introduces how to install the Vapor command-line tool on mac 10.5.4 and ubuntu 18.04, create Vapor projects, debug projects simply, and how to deploy to a production environment. The runtime environment mentioned in the article includes: Vapor 4, Swift 5.2, Catalina 10.5.4, Ubuntu 18.04.

I recently set up a new cloud server (Linux system) and used Hexo to create a new blog website. Considering adding some simple interactive features, I need to add logic processing capabilities on the server side. Having mainly used Swift for the past six months, I decided to try Server Side Swift. Without paying much attention to choosing from various frameworks, after watching a few videos introducing Vapor, I felt good about it and started to attempt installation and configuration.

The official documentation of Vapor really has some problems, and many experiences and tutorials online are a bit outdated (there have been significant version changes recently). After two days of struggling, I finally managed to figure out the main points and initially set up both development and production environments.

What is Vapor

Vapor is a web framework written in Swift, cross-platform (mac, Linux), allowing users to efficiently complete various network services using Swift and its rich third-party libraries.

Swift
import Vapor

let app = try Application()
let router = try app.make(Router.self)

router.get("hello") { req in
    return "Hello, world."
}

try app.run()

The above code can complete a basic network service. Visiting http://localhost:8080/hello returns hello, world.

You can develop on mac or Linux platforms, and also deploy projects developed with the Vapor framework to mac or Linux platforms.

What is Vaper Command Line Tool

The Vaper command-line tool functions include:

  • Creating Vaper projects based on templates
  • Configuring, compiling, and running projects
  • Other features that work with the operating system

However, it is not essential. If users are already familiar with the development and configuration of Vapor, they can do without this command line tool in both development and running environments. But for beginners like me, it undoubtedly is a helpful tool.

Resources Needed for Developing Vaper Projects

  • mac or Linux, I have successfully completed configurations under Catalina 10.5.4 and Ubuntu 18.04
  • Swift language environment, currently Vapor 4 can run under Swift 5.2
  • Web server, I am currently using Nginx (it can be not configured if only for development and testing)

Installing Swift

Mac

On mac platforms, install Xcode and Xcode Command Line Tools. Vapor 4 provides very friendly support for Xcode, allowing you to use all kinds of capabilities of Xcode like other Swift projects (such as breakpoint debugging).

Ubuntu 18.04

Bash
sudo apt-get install clang
sudo apt-get install libcurl3 libpython2.7 libpython2.7-dev 

# Find the required file from swift.org

wget https://swift.org/builds/swift-5.2.3-release/ubuntu1804/swift-5.2.3-RELEASE/swift-5.2.3-RELEASE-ubuntu18.04.tar.gz

tar xzvf swift-5.2.3-RELEASE-ubuntu18.04.tar.gz
sudo mv swift-5.2.3-RELEASE-ubuntu18.04 /usr/share/swift

echo "export PATH=/usr/share/swift/usr/bin:$PATH" >> ~/.bashrc
source  ~/.bashrc

I have also used Docker to install Swift 5.2, but the Image is quite large, requiring about 1.2 Gb of space.

Bash
# Method to install Swift with Docker.
docker pull swift

Installing Vapor Command Line Tool

MacOS

Shell
brew tap vapor/tap
brew install vapor
# I currently installed vapor-beta
# brew install vapor-beta
# Test vapor
vapor

Ubuntu 1804

Installing the Vapor toolbox on Ubuntu is slightly cumbersome, mainly because the current Vapor Toolbox source files have some issues and need some modifications to compile properly.

First, ensure that Swift is successfully installed

Bash
cd ~
git clone https://github.com/vapor/toolbox.git
cd toolbox

Create a LinuxMain.swift file in the Test directory

Swift
import XCTest
@testable import AppTests
XCTMain([testCase(AppTests.allTests)])

This is a file needed by Swift SPM. I just wrote the simplest one that can complete the compilation. I’m not sure why the official git source does not include this file.

Modify the

Source/VaporToolbox/exec.swift file

Around line 36 (current version), find

Swift
let spawned = posix_spawnp(&pid, argv[0], &fileActions, nil, argv + [nil], envp + [nil])

Change it to

Swift
guard let _argv0 = argv[0] else {
            fatalError("unwrap error")
        }
let spawned = posix_spawnp(&pid, _argv, &fileActions, nil, argv + [nil], envp + [nil])

Again, I’m not sure why there is such an error in the code.

After completing the above modifications

Bash
cd ~/toolbox
swift build -c release --disable-sandbox
sudo mv .build/release/vapor /usr/local/bin

The above steps worked on my local Ubuntu, but on my Tencent cloud server, there was a missing library during compilation, which after being added allowed normal compilation.

Bash
sudo apt-get install libcurl4 -y

Thus, the installation of Vapor Toolbox is complete.

The directory of Toolbox contains a Dockerfile, which can directly create a Docker Image of toolbox, and the system will automatically download the Docker Image of Swift. However, using this method on Ubuntu (entrypoint configured), the vapor Image had no name, only a container id, but it can be run through the id. I currently do not recommend this method.

Using Vapor Command Line Tool

Creating a Project

Bash
#vapor new <projectname> [--template]
vapor new hello

Create a Vapor project named ‘hello’ using the default template. This process essentially clones a template from GitHub and assists with basic configuration. For those already familiar, one can start a project directly by cloning a template from GitHub without using the tool.

On macOS, templates can be directly compiled and run. However, on Linux, the git source still lacks the LinuxMain.swift file. As mentioned above, adding this file to the project allows compilation.

After cloning, the system prompts as follows:

Bash
Would you like to use Fluent? y
Database type selection #I chose sqlite

The system automatically creates corresponding code in the template based on your selection. (Fluent is an ORM written in Swift.)

Compiling the Project

Bash
cd ~/hello
vapor build

On ubuntu, vapor new executes normally, but vapor build throws an error, so I use swift build to compile the project. In fact, build and run directly invoke the swift command.

Running the Project

Bash
vapor run 
#The system display indicates that the project has started successfully. Accessible at http://127.0.0.1:8080
Environment(name: "development", arguments: [".build/x86_64-apple-macosx/debug/Run"])
[ NOTICE ] Server starting on http://127.0.0.1:8080

Add runtime states after the run command, which is particularly important for deployment.

Bash
vapor run --env prod 
# test prod dev correspond to different states, mainly related to whether to display operational logs

For macOS, use

Bash
vapor xcode 

to directly open Xcode, where you can edit, compile, debug, and run under Xcode.

Even without installing Vapor Toolbox, you can create an Xcode project using:

Bash
swift package generate-xcodeproj

Following these steps, we can start writing and running our own Vapor projects on both macOS and Ubuntu.

Brief Analysis of the Template Project

In this section, we quickly experience the convenience of Vapor through a simple analysis of the template code.

I used the default project template, enabled Fluent, and chose sqlite as the database.

The project source files are located in the Sources directory.

Directory Structure

main.swift serves as the entry point of the program, creating the Vapor service.

configure.swift contains autogenerated code for database usage since we chose sqlite. The following code completes the creation of the database:

Swift
app.migrations.add(CreateTodo())

To fully run this template project, execute the following command in the terminal:

Bash
vapor run migrate

This completes the creation of tables in the db.sqlite file in the project root directory. Without this step, accessing localhost:8080/todos results in an error:

Bash
[ ERROR ] error: no such table: todos

Xcode users can also directly add —auto-migrate in the Scheme’s Arguments to accomplish the above.

Additionally, it’s best to set the Scheme — Run — Working Directory in Xcode to the project’s root directory. This ensures that whether using the command line or Xcode, the same Sqlite file is used.

Swift
//app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))

Uncommenting this line enables Vapor to support static files. Static files placed in the Public directory of the project root can be accessed at 127.0.0.1:8080/index.html. If using in conjunction with other WebServers, it might be better to use something like Nginx for static file support.

routes.swift, as the core of the project, sets up the web routing logic:

Swift
import Fluent
import Vapor

func routes(_ app: Application) throws {
    /*
    Access 127.0.0.1:8080/ Returns: "It works!"
    */
    app.get { req in
        return "It works!"
    }
    
    //   localhost:8080/hello    Returns: "Hello, world!"
    app.get("hello") { req -> String in
        return "Hello, world!"
    }

    let todoController = TodoController()
    /*
    The following operations were tested using Postman
    post 127.0.0.1:8080/todos with body content: {"title":"Dongpo's Elbow"} to add a record
    get 

127.0.0.1:8080/todos to display the added records
    del 127.0.0.1:8080/todos/B508471F-FF5F-422C-B384-C300FD7B49D9 to delete a record. The id is obtained from the displayed record
    */
    app.get("todos", use: todoController.index)
    app.post("todos", use: todoController.create)
    app.delete("todos", ":todoID", use: todoController.delete)
}

Further application details are not expanded upon here. However, even from the template example alone, we can feel the convenience and efficiency of Vapor.

Using with Nginx

By editing the Nginx configuration file, our Vapor project can now be deployed to the public.

nginx
server {
        listen       80;
        server_name  localhost;
  
        location / {
          root   html;
          index   index.html index.htm;
          try_files  $uri @proxy;
        }

        location @proxy {
               proxy_pass http://127.0.0.1:8080;
               proxy_pass_header Server;
               proxy_set_header Host $host;
               proxy_set_header X-Real-IP $remote_addr;
               proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
               proxy_pass_header Server;
               proxy_connect_timeout 3s;
               proxy_read_timeout 10s;
}

Now, you can access your project via http://yourdomainoraddress/todos.

I use Vapor to complement my blog, so it still needs to work in conjunction with my existing pages. Hence, I’ve adopted the following configuration.

Even with Vapor’s static page support, if I configure my Vapor project at the root (I’ve discontinued Vapor’s response to root), I still need to explicitly enter http://mydomain/index.html to access the index page. That’s why I moved it under /api/.

nginx
server {
        listen       80;
        server_name  localhost;
  
        location / {
            root   html;
            index  index.html index.htm;
        }

        location /api {
          root   html;
          index   index.html index.htm;
          try_files  $uri @proxy;
        }

        location @proxy {
               proxy_pass http://127.0.0.1:8080;
               proxy_pass_header Server;
               proxy_set_header Host $host;
               proxy_set_header X-Real-IP $remote_addr;
               proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
               proxy_pass_header Server;
               proxy_connect_timeout 3s;
               proxy_read_timeout 10s;
}

After such configuration, you need to modify the code in routes.swift to access it normally.

Swift
app.get("api","hello") { req -> String in
        return "Hello, world!"
    }

If anyone knows how to set it up so that / is forwarded directly to Vapor and still use http://mydomain to access the original pages, please let me know.

Deployment

Modifying the Running Port

Vapor 4 has significant differences in specifying the running port compared to before.

Make the following changes in main.swift:

Swift
app.http.server.configuration.hostname = "127.0.0.1" //Address to respond to 0.0.0.0 
app.http.server.configuration.port = 8000 //The port you wish to set
try configure(app)

Currently, I haven’t found how to set the running port from the command line (the method before Vapor 3 seems no longer supported). If anyone knows, please share.

Manual Deployment

Since I’m also new to Vapor, for debugging convenience, I develop under Xcode on my machine. Using GitHub as an intermediary, I commit local modifications to the repository. Manually fetch and execute on the server side. If executed in the terminal, the current terminal will be locked by the task.

Docker Deployment

Additionally, the Vapor template itself has already generated a Dockerfile. You can also directly create a Docker Image from the completed project. This method allows the project to be deployed on any Docker-supported platform (mac, Linux, windows, etc.). However, it’s usually suitable for deployment only after development is completed. For more details, refer to the official documentation.

Supervisor

Vapor Toolbox already provides support for Supervisor, allowing easy management of services through Supervisor.

Installing Supervisor on Ubuntu:

Bash
sudo apt-get update
sudo apt-get install supervisor

We need to create a Supervisor configuration file for each project. Create /etc/supervisor/conf.d/hello.conf:

Bash
[program:hello]
command=/home/parallels/hello/.build/release/Run serve --env production
directory=/home/parallels/hello
user=parallels
stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log
stderr_logfile=/var/log/supervisor/%(program_name)-stderr.log

The file name is your project name followed by .conf, with the directory pointing to the root of your project and the username set correctly.

Bash
command=/home/parallels/hello/.build/release/Run serve --env production

Ensure that the project is compiled into a release version. If vapor build doesn’t work, you can use the following command:

Bash
cd

 ~/hello
swift build -c release

Managing the project through Supervisor:

Bash
supervisorctl reread
supervisorctl add hello
supervisorctl start hello

You can also specify the running port through Supervisor’s configuration.

Add to /etc/supervisor/conf.d/hello.conf:

Bash
environment=PORT=8123

Modify main.swift:

Swift
let port = Environment.get("PORT") ?? ""
app.http.server.configuration.port = Int(port) ?? 8080

Conclusion

I hope this article helps you get started with Vapor 4. At the same time, I also hope to see Swift perform on more platforms.

Swift has shown more official support for Windows.

I'm really looking forward to hearing your thoughts! Please Leave Your Comments Below to share your views and insights.

Fatbobman(东坡肘子)

I'm passionate about life and sharing knowledge. My blog focuses on Swift, SwiftUI, Core Data, and Swift Data. Follow my social media for the latest updates.

You can support me in the following ways