Home Server-Side Swift Tutorials

gRPC and Server Side Swift: Getting Started

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

Version

  • Swift 5.5, Multiplatform, Xcode 13

gRPC tools are powerful when working across many teams or in distributed teams. This is because, in addition to server-side Swift, the gRPC tools can generate code for Swift, Objective-C, Java, Kotlin and many other languages.

Working with gRPC allows you to define an API and generate networking and object model code for use on your server and any client.

In this tutorial you’ll learn how to define an API based on gRPC and how to leverage its tools to generate code for Server Side Swift and client apps. You’ll learn how to:

  • Read and change a .proto file that describes an API.
  • Exercise a gRPC API with Evans.
  • Use the protoc command line tool to generate Swift code for your server.
  • Replace the default HTTP server in a Vapor app with a gRPC server.
  • Use gRPC to provide a CRUD service based on Fluent and PostgreSQL.
Note: This tutorial assumes you’ve installed Docker and have some basic experience with it. See Developing and Testing Server-Side Swift with Docker and Vapor to learn more about using Docker with Vapor.

This tutorial also assumes you have experience using Vapor to build web apps. See Getting Started with Server-Side Swift with Vapor 4 if you’re new to Vapor.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of this tutorial.

The starter project contains a Vapor app for managing TODO items. Some of the pieces aren’t there as you won’t be using the webserver capabilities of Vapor.

Along with the project, you need to download and install other tools for working with gRPC. The sections below detail the steps for installing protoc, Evans, and the grpc-swift plugins on macOS or Linux. Skip ahead if you already have them installed.

In this tutorial, you’ll use Swift Package Manager to set gRPC as a dependency for the Vapor server. You’ll use the protoc code generator and Swift language plugins to generate Swift code from a .proto file. Finally, you’ll use Evans to act as the client for your server.

Installing Evans

Evans is the gRPC version of something like cURL or Insomnia which allows you to make calls to a server that uses gRPC without having to write an entire client app first. Evans is also a quick way to check a .proto file for errors.

On macOS, Evans is available via Homebrew. To install it, type the following in Terminal:

brew tap ktr0731/evans && brew install evans

On Linux the builds are available on GitHub.

After you install Evans, confirm it’s working by checking the output of this command in the Terminal:

evans --version

Installing Protoc and the Swift Plugins

To generate the Swift code from a gRPC spec file you need:

  • The protoc executable.
  • The plug-ins for Swift: one for generating Swift code for Message items and one for generating Service items.

On macOS, both are available in homebrew. In Terminal, type:

brew install swift-protobuf grpc-swift

The GitHub repo for grpc-swift contains more tutorials and demos of other features of the Swift implementation of gRPC, you may want to clone this to get the Swift translators for Linux. If you are running macOS, you can skip the rest of this section.

For Linux, protoc is available as a binary from GitHub. After extracting the binary and placing it somewhere in your PATH, confirm that everything works by checking on the version by typing the following into Terminal:

protoc --version

On Linux, protoc comes with translators for Java, Kotlin, Python, and other languages, but not Swift. To get the Swift translators for Linux, clone the Swift gRPC project.

git clone https://www.github.com/grpc/grpc-swift <some directory>

The project has a number of make scripts. Generate the plugins by navigating to the root of the project and typing:

make plugins

Now, copy the protoc-gen-grpc-swift and protoc-gen-swift files into the same directory as your protoc executable.

Whew! All the tools are in place, and it’s time to write some code. :-]

Adding gRPC Support to Vapor

For Swift, gRPC runtime support is available as a standard Swift package. Open the Package.swift file in the sample project and add the following to the dependencies array:

.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.0.0"),

Next, in the app dependencies section add a reference to the package:

.product(name: "GRPC", package: "grpc-swift"),

Now, build the project to ensure that all packages have been correctly configured.

Open Terminal, navigate to the root of the project and type the following:

swift build

This will pull all the dependencies and build your Vapor app. This process can take a while, so in the meantime, you can learn some more details about gRPC.

Learning About gRPC

gRPC is a technology developed at Google to help you define and maintain an app interface. It has tools and plugins that generate native networking and object models code for the server and client to use with the interface.

It has many similarities to OpenAPI. Both are solving a similar problem; how to build, document and maintain APIs that are usable by different clients in different languages and at different organizations.

gRPC generates server and client code from the same spec, encoded in a .proto file, regardless of the programming language used.

This helps ensure that the code is less prone to errors caused by typos in a URL or field name. Since gRPC handles the network transport, developers don’t have to write specific code to support network calls.

gRPC process of code generation

How is gRPC Different from REST?

The most common way you’ve probably worked with APIs is using a REST interface with JSON. These technologies were initially built for web browsers to talk to web servers. Servers with RESTful APIs organize around URL paths and use HTTP verbs such as; GET, POST etc., to determine what resource to send to the client.

When working with a RESTful server, you usually have to encode and decode JSON data and have HTTP stack functions. Any client that can read JSON data over an HTTP connection can use a RESTful API.

gRPC sets out to make the calls to a remote server look and act like calls to other local parts of an app. Instead of constructing a URL and encoding JSON to send over HTTP, your app calls a function or method and gRPC handles encoding the data to a compact format, then transmits the data.

Additionally, gRPC uses HTTP/2 which allows for APIs to stream data. You won’t stream data in this tutorial, but there are examples in the grpc-swift repo.

Learning the Difference Between gRPC and JSON?

For encoding and decoding data objects, gRPC uses Protocol Buffers.

Unlike the plain text files used by JSON, Protocol Buffers are a binary file type, not human readable. They enable the exchange of smaller payloads, which are more efficient and suitable for low bandwidth use cases.

To get started, you must define gRPC messages and data models in a .proto file.

Working with a .proto File

A .proto file is a text file containing definitions for all your APIs. In this tutorial the domain revolves around Messages and Services. Messages are the data objects that get sent between the client and server. Services define how messages get transported. A .proto file can also have some metadata to help the various code generators.

Most exciting to anyone who has ever struggled with comments in JSON: a .proto file can have comments! It supports both // style and /* ... */ style comments!

Open the todo.proto file in the root directory of the project to explore it a bit. The first line of the file specifies the version of gRPC to use. It looks like this:

syntax = "proto3";

If this line is missing, the gRPC system will assume that you’re using version 2, not version 3. After that, some options will assist the code generator in naming things.

Defining the Services

Next, you’ll see the service definition. The rpc entries will each become a func in the generated Swift code.

// The todos service definition.
service TodoService {
  // Return a list of todos.
  rpc FetchTodos (Empty) returns (TodoList) {}
  // Create a new todo
  rpc CreateTodo (Todo) returns (Todo) {}
  // Delete a todo
  rpc DeleteTodo (TodoID) returns (Empty) {}
}

Adding code to every generated func is how you’ll make the server work. Every rpc must have a request and a response message. That’s why FetchTodos sends an Empty message rather than something like FetchTodos().

Defining the Messages

Here is the message spec for the Todo object:

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

This code defines two fields; title and todoID. It also ensures that they’re of type string.

The first field is marked as optional. It won’t have a value until it’s been stored in the database. The code generator keeps track of each field in a message by assigning it a number.

In addition to the string type, a field can be many different scalar types and each language generator will make the appropriate translations. Fields can be enumerations or even other messages. For example, below is the definition of TodoList, which is a collection of Todo instances.

message TodoList {
  repeated Todo todos = 1;
}

The repeated keyword means that the code will generate an array of Todos. You can learn more about the different options in the proto3 language guide.

Google provides many well-known types including: Empty, Timestamp, BoolValue and many more. You can read the documentation to learn about them. Since the homebrew version of protoc isn’t bundled with them, this tutorial uses a custom one. Both strategies, using Google’s or making your own, are common.

Note: As your API evolves, you must never reuse numbers. The gRPC generation code uses the numbers to ensure that different versions of your API stay compatible as much as possible.

Exercising a .proto File With Evans

In a Terminal window, navigate to the root directory of the project. This is the directory that contains the todo.proto file. Launch Evans in REPL mode using this command:

evans repl --host localhost --port 1234 --proto ./todo.proto

You’ll see the Evans prompt, similar to this:

  ______
 |  ____|
 | |__    __   __   __ _   _ __    ___
 |  __|   \ \ / /  / _. | | '_ \  / __|
 | |____   \ V /  | (_| | | | | | \__ \
 |______|   \_/    \__,_| |_| |_| |___/

 more expressive universal gRPC client


todos.TodoService@localhost:1234> 

Even though you don’t have a running gRPC server yet and haven’t generated the gRPC Swift code, Evans uses the todo.proto file to provide information about the services.

Recall from the todo.proto file that you have four messages: Empty, Todo, TodoList and TodoID. The file also defines three services: FetchTodos, CreateTodo and DeleteTodo. Use Evans to inspect these.

Now type the following command in the Evans prompt:

show message

You’ll see the four defined messages, like this:

+----------+
| MESSAGE  |
+----------+
| Empty    |
| Todo     |
| TodoID   |
| TodoList |
+----------+

Now type show service to see the list of services. Evans will show:

+-------------+--------------+--------------+---------------+
|   SERVICE   |     RPC      | REQUEST TYPE | RESPONSE TYPE |
+-------------+--------------+--------------+---------------+
| TodoService | FetchTodos   | Empty        | TodoList      |
| TodoService | CreateTodo   | Todo         | Todo          |
| TodoService | DeleteTodo   | TodoID       | Empty         |
+-------------+--------------+--------------+---------------+

To close Evans and return to Terminal type: exit.

You are on the right track. The next step is to generate some Swift code.

Generating Swift Code From a .proto file

Make sure Terminal is in the directory with the todo.proto file and type the following:

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

This command will create two files: Sources/App/Models/todo.pb.swift and Sources/App/Controllers/todo.grpc.swift. The first is for the Message items, the second is for the Service items. You’ll not be editing these files directly, and whenever the API changes, you’ll need to regenerate them. If the files don’t appear in your IDE, you may need to close and reopen the project to refresh the list of files.

There are a number of other options available but the ones specified above will generate the two swift files for this tutorial.

Note: The grpc-swift plugin does not create async/await code by default and does not yet officially support async/await. One of the options for protoc will generate async/await style code based on a proposed implementation. You can read more about this option and the async/await code at the GitHub repository for the project.

Messages Become Structs

Open the todo.pb.swift file. In addition to generating Swift versions of properties of a Todo, the new code also has a convenience method to work with the optional todoID property, as displayed in the excerpt below.

struct Todos_Todo {
  // SwiftProtobuf.Message conformance is added in an extension below. See the
  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for
  // methods supported on all messages.

  var todoID: String {
    get {return _todoID ?? String()}
    set {_todoID = newValue}
  }
  /// Returns true if `todoID` has been explicitly set.
  var hasTodoID: Bool {return self._todoID != nil}
  /// Clears the value of `todoID`. Subsequent reads from it will 
  /// return its default value.
  mutating func clearTodoID() {self._todoID = nil}

  var title: String = String()
 
  var unknownFields = SwiftProtobuf.UnknownStorage()
  
  init() {}
  fileprivate var _todoID: String? = nil
}
Note: It’s important to remember that this code is all generated, so never edit the .pb.swift or .grpc.swift files as they’ll eventually be overwritten.

In addition to the clearTodoID() convenience function, the comment indicates that there are many more convenience methods for Message structs. The best way to explore them is to open the swift-protobuf package and read the comments in the Messages.swift and the Messages+*.swift files. There you’ll find a number of convenience methods for working with JSON, text and data objects.

Notice how gRPC combines the package name with the message name when creating the Swift structure. This naming convention occurs throughout all of your gRPC code. Though it looks a little out of place in Swift code, it does help you remember when you’re working with the gRPC objects versus any other objects.

Working with Generated Code

If you’ve worked with other code generators before, you’ll be familiar with the pattern of using extensions and type aliases to separate your code from the generated, and often overwritten, code. For the sake of code organization, open Sources/App/Models/Todo.swift and replace //TODO: Create initializer methods with the following:

extension Todos_Todo {
  init (_ todo: Todo) {
    if let todoid = todo.id {
      self.todoID = todoid.uuidString
    }
    self.title = todo.title
  }
}

This will help you to work directly with the Fluent model. Since the id property is optional, you don’t want to set it unless your object already has one.

Now, add an extension to initialize a Todo with a Todos_Todo. At the end of the Todo.swift file, add the following code:

extension Todo {
  convenience init (_ todo: Todos_Todo) {
    self.init(id: UUID(uuidString: todo.todoID), title: todo.title)
  }
}

Having these two initializers allows you to convert your Todo models between the form that the PostgreSQL server wants and the form that gRPC wants.

Build the app again to make sure everything is still correct. The command is the same you have already used:

swift build

With the data model in place, now it’s time to check out the services.

Turning Services Into Functions

Now, open Sources/App/Controllers/todo.grpc.swift and find the section that defines the protocol Todos_TodoServiceProvider. The spec in the proto file has been turned into Swift code, and the comments have also carried over!

By default, protoc generated client and server code in this file. There are options to generate only client or server code.

To make your server functional, you need to create a class that implements this Todos_TodoServiceProvider protocol.

In the Controllers folder, add a new file called TodoProvider.swift. Calling something a ‘Provider’ isn’t really common in Swift, but it’s the convention when working with gRPC.

In your new file, add the following imports:

import Foundation
import GRPC
import Vapor
import Fluent

Next, add the following class:

class TodoProvider: Todos_TodoServiceProvider {
  var interceptors: Todos_TodoServiceServerInterceptorFactoryProtocol?
 
  //TODO: Add reference to Vapor

  func fetchTodos(request: Todos_Empty, context: StatusOnlyCallContext) 
    -> EventLoopFuture<Todos_TodoList> {
   //TODO: Add fetchTodos 
  }

  func createTodo(request: Todos_Todo, context: StatusOnlyCallContext) 
    -> EventLoopFuture<Todos_Todo> {
   //TODO: Add createTodo 
  }

  func deleteTodo(request: Todos_TodoID, context: StatusOnlyCallContext) 
    -> EventLoopFuture<Todos_Empty> {
   //TODO: Add deleteTodo 
  }
}

The name, request type and return type are all recognizable from the .proto file. In addition, there’s a context parameter, which provides you with a logger, the event loop for the call as well as the ability to set a response status and some other properties.

Next, you’ll write functions for the service provider.

Implementing Service Functions

Before you start adding code to the functions, add a reference to the Vapor app itself so you can work with the database. Replace //TODO: Add reference to Vapor with:

var app: Application
  
init(_ app: Application) {
  self.app = app
}

This code adds a variable to hold a reference to the Vapor app and adds an init method so you can pass in the app when everything starts up.

The next step is to add the code for each of the functions. You’ll query the database for each function and return data or an error.

Replace //TODO: Add fetchTodos with:

let todos = Todo.query(on: app.db(.psql)).all()
              .map { todos -> Todos_TodoList in //1
  var listToReturn = Todos_TodoList()
  for td in todos {
    listToReturn.todos.append(Todos_Todo(td)) //2
  }
  return listToReturn
  }

return todos //3

Here’s what this code is doing:

  1. Queries for all the TODO records and passes the results into a .map.
  2. Loops through the TODO records and converts each one into a gRPC Todos_Todo object using the initializer you created earlier.
  3. Returns the array of gRPC TODOs.

Now replace //TODO: Add createTodo with:

let todo = Todo(request) //1
return todo.save(on: app.db(.psql)).map { //2
  Todos_Todo(todo) //3
}

Here’s what this code is doing:

  1. Converts the gRPC Todos_Todo in the request to a Todo to use with PostreSQL.
  2. Saves the new todo to the database.
  3. Converts the todo into the gRPC format and sends it back to the client.

The function to delete is a little longer because it validates the request and needs to find the corresponding entry in the database before it can delete it. Replace //TODO: Add deleteTodo with the following:

guard let uuid = UUID(uuidString: request.todoID) else { //1
  return context.eventLoop.makeFailedFuture(
    GRPCStatus(code: .invalidArgument, message: "Invalid TodoID")) //2
}
return Todo.find(uuid, on: app.db(.psql)).unwrap(or: Abort(.notFound))
  .flatMap { [self] todo in //3
    todo.delete(on: app.db(.psql))
      .transform(to: context.eventLoop.makeSucceededFuture(Todos_Empty())) //4
}

Here’s what this code is doing:

  1. Attempts to make a UUID out of the .todoID property of the request.
  2. If a UUID can’t be made, creates a failed future to pass back with a gRPC error object.
  3. Finds the Todo record in the database using the uuid.
  4. Deletes the record and returns a succeeded future.

Whew, that’s a lot of code. Run the build command again to make sure everything still builds with no issues:

swift build

The expected output is something like this:

starter> swift build
[5/5] Build complete!
starter> 

Now it’s time to integrate gRPC in the existing Vapor app.

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.

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

You’ll see a list similar to this one with old commands that contain the word ‘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> 

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.

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).

Adding the Completion Logic

Open TodoProvider.swift and add the new function to complete a TODO. The function body will look similar to that of deleteTodo(request, response). It searches by ID and then it toggles the completed field and saves the record using .update instead of .delete. It also returns the updated Todos_todo in the response instead of Empty. See if you can figure out the code. If you need help just open the spoiler below.

[spoiler title=”Solution”]

func completeTodo(
  request: Todos_TodoID, 
  context: StatusOnlyCallContext
) -> EventLoopFuture<Todos_Todo> {
  guard let uuid = UUID(uuidString: request.todoID) else {
    context.responseStatus.code = .invalidArgument
    return context.eventLoop.makeFailedFuture(
      GRPCStatus(code: .invalidArgument, message: "Invalid TodoID"))
  }
  return Todo.find(uuid, on: app.db(.psql))
    .unwrap(or: Abort(.notFound)).flatMap { [self] todo in
      todo.completed = !todo.completed
      return todo.update(on: app.db(.psql)).transform(to:
        context.eventLoop.makeSucceededFuture(Todos_Todo(todo)))
  }
}

[/spoiler]

Open the Todo.swift file and replace //TODO: Add field for task completed with:

@Field(key: "completed")
var completed: Bool

Then, add the completed field to the three init methods, so that your final code looks like this:

  init(id: UUID? = nil, title: String, completed: Bool? = false) {
    self.id = id
    self.title = title
    self.completed = completed ?? false
  }
}

extension Todos_Todo {
  init (_ todo: Todo) {
    if let todoid = todo.id {
      self.todoID = todoid.uuidString
    }
    self.completed = todo.completed
    self.title = todo.title
  }
}

extension Todo {
  convenience init (_ todo: Todos_Todo) {
    self.init(id: UUID(uuidString: todo.todoID), title: todo.title, 
              completed: todo.completed)
  }
}

Updating the Database Model

The next step is to add the field to the PostgreSQL database. The code is already in the project, in the file AddTodoCompletion.swift. Open the configure.swift file and uncomment the line app.migrations.add(AddTodoCompletion()).

Stop the app if it’s running and exit Evans if it’s open. Make sure the database server is still up.

Once you have done that, build the app with docker-compose build and run it with docker-compose up app. The app will auto-migrate.

Now, restart Evans and call FetchTodos to see your list. The output will look like this:

todos.TodoService@localhost:1234> call FetchTodos
{
  "todos": [
    {
      "title": "mytitle",
      "todoID": "BFC263CB-219A-4A6C-B1A0-B873E17E9969"
    }
  ]
}

Copy one of the todoID values and then call CompleteTodo and paste the UUID into the todoID field.

todos.TodoService@localhost:1234> call CompleteTodo
todoID (TYPE_STRING) => BFC263CB-219A-4A6C-B1A0-B873E17E9969
{
  "completed": true,
  "title": "mytitle",
  "todoID": "BFC263CB-219A-4A6C-B1A0-B873E17E9969"
}

Now type call FetchTodos and notice your TODO is marked as completed, like this.

todos.TodoService@localhost:1234> call FetchTodos
{
  "todos": [
    {
      "completed": true,
      "title": "mytitle",
      "todoID": "BFC263CB-219A-4A6C-B1A0-B873E17E9969"
    }
  ]
}

todos.TodoService@localhost:1234> 

You may notice that, in Evans, when the completed field is false it doesn’t appear in the Todo. This is a feature of proto3 as boolean values default to false. So, the server doesn’t send default values. Open todo.pb.swift and notice in the Todos_Todo struct that the completedvar is set to false when it gets created. By not sending default values back and forth, the client and server are able to save a little space.

It did take some work to modify the API; however, since you work with generated code instead of “stringly” typed URLs and JSON, the compiler and the IDE help you ensure that your code continues to match the API as it changes. Congrats!

Where to Go from Here?

The completed project is in the demo project’s final folder. You can download the project file if you haven’t already by clicking the Download Materials button at the top or bottom of this tutorial.

In this tutorial, you learned the basics of working with a .proto file to describe a gRPC API. You also learned how to generate Swift code using protoc and how to modify a Vapor app to use gRPC instead of HTTP.

The GitHub projects for protobuf, grpc-swift and protoc all contain more documentation and tutorials:

Here are a few things you can try to expand the current app:

  • Use the todo.pb.swift and todo.grpc.swift files to create an iOS or macOS client for your Vapor service.
  • Generate gRPC code in some other programming language and create a client for your Vapor service.
  • Add some more fields to the existing messages or services.

Please join the forum discussion below if you have any questions or comments!

Contributors

Comments

Reviews

More like this