gRPC and Server Side Swift: Getting Started

Learn how to define an API with gRPC and how to integrate it in a Vapor application. By Walter Tyree.

5 (2) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 4 of 5 of this article. Click here to view the first page.

Replacing the HTTP Server in Vapor

There’s one final step before you can build and run your new service. By default, Vapor uses an HTTP server. However, it will use any class that conforms to the Vapor.Server protocol.

Open the GRPCServer.swift file. It already contains the boilerplate code for your server to conform to the Vapor.Server protocol. If you want to read more, here is a link to the documentation.

Configuring the gRPC Server

First, import the GRPC package by adding this at the top of the file:

import GRPC

Within GRPCServer, find the line that sets the server variable and specify that it will be a GRPC server. Your list of vars should now look like this:

var port = 1234
var host = "localhost"
var application: Application
var server: GRPC.Server?

Notice that the default port for the server is 1234. The swift-grpc project uses that port in all of their examples. As long as the server and the clients know which port to use, any available port is fine.

Now find the comment //TODO: Add Spin up gRPC server and replace it with the following:

//set up logging 
var logger = Logger(label: "grpc", factory: StreamLogHandler.standardOutput) //1
logger.logLevel = .debug

//bind to host and port. do not use SSL
let group = application.eventLoopGroup
let server = GRPC.Server.insecure(group: group) //2
  .withLogger(logger)
  .withServiceProviders([TodoProvider(application)]) //3
  .bind(host: self.host, port: self.port)
  server.map { //4
    $0.channel.localAddress
  }.whenSuccess { address in
    logger.debug("gRPC Server started on port \(address!.port!)", 
                 metadata: nil)
  }

self.server = try server.wait() //5

Here’s what this code is doing:

  1. Creates a standard logger for the server to use.
  2. Initializes the server using Vapor’s eventLoopGroup and doesn’t use SSL.
  3. Tells the server to use TodoProvider. A single gRPC server can handle multiple providers generated from multiple .proto files.
  4. The server gets created as a future, so you’ll wait for it to start and then log the port as a sign that everything worked.
  5. Saves a reference to the server so it can be shut down later.

At the bottom of the file, find the shutdown() function and uncomment the code inside it.
Then find the var named onShutdown and replace the whole return statement with:

return server!.channel.closeFuture

Now it’s time to use your new gRPC server!

Using the gRPC Server

Open the configure.swift file and uncomment the line, which sets up the gRPC server and instructs the Vapor app to use it:

app.servers.use(.gRPCServer)

Now, you’ll be able to use docker-compose to launch the database server and your new gRPC Vapor server. You may want to open a new Terminal window or tab for each to keep things organized. Make sure that the database is running first.

docker-compose up db

This will launch the PostgreSQL database server.

When the database server is up, run the app using the following command:

swift run

This will first build the Vapor app and then run it.

Trying out the App

With the database server running, the migrations completed, and the app running, you’re ready to test everything. Relaunch Evans using the same command as before.

You’ll see a list similar to this one with old commands that contain the word ‘Evans.’

If you want to execute the command 494 again, type in Terminal !494 and that command will appear at the prompt ready for you to edit or run.

Note:
A trick to avoid having to retype long commands is to use history and grep. In Terminal, be sure you’re in the same directory as your todo.proto file and type:
history | grep evans
  ...
  485  evans repl --host localhost --port 1234 --proto ./todo.proto
  489  history | grep evans
  490  evans repl --host localhost --port 1234 --proto ./todo.proto
  494  evans repl --host localhost --port 1234 --proto ./todo.proto
  501  history | grep evans
starter> 
history | grep evans
  ...
  485  evans repl --host localhost --port 1234 --proto ./todo.proto
  489  history | grep evans
  490  evans repl --host localhost --port 1234 --proto ./todo.proto
  494  evans repl --host localhost --port 1234 --proto ./todo.proto
  501  history | grep evans
starter> 

Now, get the list of Todo items from the database by typing the following in the Evans prompt:

call FetchTodos

Evans will return an ‘Empty’ message since there aren’t any records in the database yet.

todos.TodoService@localhost:1234> call FetchTodos
{}
todos.TodoService@localhost:1234> 

Time to add some entries!

Evans is interactive, so when you execute call CreateTodo, it’ll give you prompts for each field. Just press ‘Enter’ on your keyboard to leave a field blank. For the first TODO, leave the todoID blank and enter a task for the title. The server will create a new TODO item and return it, like this:

todos.TodoService@localhost:1234> call CreateTodo
✔ todoID
todoID (TYPE_STRING) => 
title (TYPE_STRING) => mytitle
{
  "title": "mytitle",
  "todoID": "11E472BA-EB72-4D38-A5D2-05E9E20293D4"
}

Now, call FetchTodos again, and you’ll see your TODO item as the only entry in the result array.

todos.TodoService@localhost:1234> call FetchTodos
{
  "todos": [
    {
      "title": "mytitle",
      "todoID": "11E472BA-EB72-4D38-A5D2-05E9E20293D4"
    }
  ]
}

todos.TodoService@localhost:1234> 

Go ahead and add some more and maybe delete some to show yourself that everything is working correctly. What about completing TODOs? Glad you asked; that’s the next feature you’ll implement!

Changing the API

Your TODO items have titles, but it would be great if you could also mark them as completed. Marking TODO items as complete is one of life’s great satisfactions. You’re going to do that now.

Updating the Spec

Open the todo.proto file and modify the Todo message to add a completed field, like this:

message Todo {
  optional string todoID = 1;
  string title = 2;
  bool completed = 3;
}

Now, add a new rpc entry to the Services, right after DeleteTodo:

// Toggle the completion of a todo
rpc CompleteTodo (TodoID) returns (Todo) {}

Save the file and, in Terminal, generate the Swift code again using the following command:

protoc --swift_out=Sources/App/Models/ --grpc-swift_out=Sources/App/Controllers/ todo.proto

To double check, open todo.pb.swift and todo.grpc.swift to see that a new field has been added to the Todos_Todo struct and that there’s a new func for completeTodo(request, context).