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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
gRPC and Server Side Swift: Getting Started
35 mins
- Getting Started
- Installing Evans
- Installing Protoc and the Swift Plugins
- Adding gRPC Support to Vapor
- Learning About gRPC
- How is gRPC Different from REST?
- Learning the Difference Between gRPC and JSON?
- Working with a .proto File
- Defining the Services
- Defining the Messages
- Exercising a .proto File With Evans
- Generating Swift Code From a .proto file
- Messages Become Structs
- Working with Generated Code
- Turning Services Into Functions
- Implementing Service Functions
- Replacing the HTTP Server in Vapor
- Configuring the gRPC Server
- Using the gRPC Server
- Trying out the App
- Changing the API
- Updating the Spec
- Adding the Completion Logic
- Updating the Database Model
- Where to Go from Here?
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 completed
var 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!