0Pricing
Learn Rust Coding · Lesson

Implementing a gRPC Server

Serve unary RPC methods.

Implementing a gRPC Server is a free Learn Rust Coding lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Learn Rust Coding learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Generated Server Trait

For a service named Greeter, tonic generates a trait in greeter_server. You implement it on your own struct to provide behavior.

The trait is async via #[tonic::async_trait], so each method is an async fn returning a Result.

use greeter::v1::greeter_server::{Greeter, GreeterServer};
use greeter::v1::{HelloRequest, HelloReply};

Request and Response Wrappers

Methods take a tonic::Request<T> and return a tonic::Response<T>. These wrappers carry metadata, extensions, and the inner message.

Call .into_inner() to get the decoded message, and Response::new(..) to build a reply.

let req: HelloRequest = request.into_inner();
let reply = HelloReply { message: format!("Hi {}", req.name) };
Ok(Response::new(reply))

A Server Struct

Define a struct to hold any shared state, like a database pool. Often it derives Default when stateless.

You will implement the generated trait on this struct.

#[derive(Default)]
pub struct MyGreeter {}

Implementing a Unary Method

Annotate the impl block with #[tonic::async_trait] and implement each rpc as an async fn matching the generated signature.

Return Ok(Response::new(reply)) on success.

#[tonic::async_trait]
impl Greeter for MyGreeter {
    async fn say_hello(&self, request: Request<HelloRequest>)
        -> Result<Response<HelloReply>, Status> {
        let name = request.into_inner().name;
        Ok(Response::new(HelloReply { message: format!("Hello {name}") }))
    }
}

Returning Errors with Status

Errors are returned as tonic::Status, which maps to a gRPC status code. Use constructors like Status::invalid_argument or Status::not_found.

The message string is sent to the client alongside the code.

if name.is_empty() {
    return Err(Status::invalid_argument("name must not be empty"));
}

Building and Serving

Use tonic::transport::Server in an async main. Add your service wrapped in its generated ...Server type, then call serve with a socket address.

The #[tokio::main] macro provides the runtime.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:50051".parse()?;
    Server::builder()
        .add_service(GreeterServer::new(MyGreeter::default()))
        .serve(addr)
        .await?;
    Ok(())
}

Server Streaming Responses

A server-streaming method returns a stream type. tonic uses an associated type plus a boxed stream; an mpsc channel is a common way to feed messages.

You return a ReceiverStream wrapped in a Response.

use tokio_stream::wrappers::ReceiverStream;
let (tx, rx) = tokio::sync::mpsc::channel(8);
tokio::spawn(async move { tx.send(Ok(reply)).await.ok(); });
Ok(Response::new(ReceiverStream::new(rx)))

Reading Metadata

Request metadata holds headers like auth tokens. Access it with request.metadata() before consuming the body.

Keys are ASCII and case-insensitive; values are returned as MetadataValue.

if let Some(token) = request.metadata().get("authorization") {
    // validate token
} else {
    return Err(Status::unauthenticated("missing token"));
}

Interceptors

An interceptor runs before each request, ideal for auth or logging. It receives the Request and returns it or a Status error.

Attach it with with_interceptor when adding the service.

fn auth(req: Request<()>) -> Result<Request<()>, Status> {
    match req.metadata().get("authorization") {
        Some(_) => Ok(req),
        None => Err(Status::unauthenticated("no token")),
    }
}
// .add_service(GreeterServer::with_interceptor(svc, auth))

Graceful Shutdown

Use serve_with_shutdown to stop accepting connections when a future resolves, such as a Ctrl-C signal.

In-flight requests finish before the server exits, avoiding abrupt drops.

Server::builder()
    .add_service(GreeterServer::new(MyGreeter::default()))
    .serve_with_shutdown(addr, async {
        tokio::signal::ctrl_c().await.ok();
    })
    .await?;

Shared State

To share mutable state across requests, store it behind Arc and a synchronization primitive like tokio::sync::Mutex inside your server struct.

tonic clones the service per connection, so cheap-to-clone shared handles are the right pattern.

use std::sync::Arc;
use tokio::sync::Mutex;

#[derive(Default)]
pub struct MyGreeter {
    hits: Arc<Mutex<u64>>,
}

Quick Check

How do gRPC methods report failures in tonic?

Recap

You implemented the generated server trait: unwrapping requests, returning responses and Status errors, serving with tokio, streaming responses, reading metadata, adding interceptors, graceful shutdown, and sharing state via Arc.

Next you will build a client to call this server.

Frequently asked questions

Is the “Implementing a gRPC Server” lesson free?

Yes — the full text of “Implementing a gRPC Server” is free to read here on the web, and the Learn Rust Coding course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Learn Rust Coding course, upgrade to CoddyKit PRO.

What will I learn in “Implementing a gRPC Server”?

Serve unary RPC methods. You practise Learn Rust Coding with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Learn Rust Coding?

No prior experience is required. Learn Rust Coding on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Implementing a gRPC Server” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Learn Rust Coding lesson?

Yes. Every Learn Rust Coding lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Protobuf and Service Definitions
  2. Generating Code with tonic-build
  3. Implementing a gRPC Server
  4. Calling from a gRPC Client
← Back to Learn Rust Coding