Skip to main content
Refine AI
gRPC vs REST - A Brief Comparison
23 min read

gRPC vs REST - A Brief Comparison

This article was last updated on September 24, 2024 to include migration strategies, security considerations, and real-world examples to provide a more comprehensive comparison between gRPC and REST.

Introduction

In this modern age, building applications with Application Programming Interfaces (APIs) that are scalable and efficient has greatly benefited businesses, developers, and users. To build an interactive application, you will need an API that enables communication and data exchange among various applications and services.

As a result, frameworks that help create high-performing APIs have been developed in recent years. Some examples of such frameworks include Representational State Transfer (REST) and Google Remote Procedure Call (gRPC).

This article will teach you about REST APIs and gRPC APIs, including their definitions, functionalities, and distinctions. Additionally, you’ll explore various scenarios that highlight practical use cases of REST and gRPC to enable you to select the most appropriate framework for your project.

Steps we'll cover:

Understanding APIs

Developers often need to integrate various services from external sources or exchange data with other applications to build highly functional applications. APIs are crucial in this process by serving as intermediaries between different systems. They define rules or protocols that facilitate communication between services or applications. By utilizing APIs, developers can access and incorporate functionalities from other applications into their own, enabling them to focus on other tasks or functionalities within their application.

Let's look at a simple example that describes APIs and their importance in application development.

grpc-vs-rest

For instance, let's say you are building a book application called BookFinder (that provides information about books) and want to design an API for it.

To enhance the functionality of your application, you decide to integrate it with a book database service by utilizing their API, which you found online. Using their API, you can access functionalities such as retrieving book details like title, description, and author.

Here's what happens: when a user searches for a book in your BookFinder application, you make an API request to the book database service, passing the search query as a parameter. The API then processes the request and returns a response containing the relevant book information.

What is a REST API?

Representational State Transfer (REST) API is a popular and widely accepted architectural style that provides a set of principles that guide the design of APIs and microservices. This approach promotes the following:

  • Scalability: REST APIs use principles such as Statelessness, which refers to a situation where the client request contains all the information needed to process the request on the server. This means that the server can handle requests from multiple clients, ensuring the system can handle increasing traffic which means scalability.

  • Loose Coupling: REST uses a standard communication protocol called HTTP and provides a common interface for the Client and Server components. As the components adhere to the common interface, REST promotes interdependence, meaning that updates or changes made to one component will not affect the others in the system. This process, known as Loose Coupling, promotes modularity and makes it easier to manage the components in the system.

Since REST API is built on HTTP (specifically the HTTP/1.1 version) protocol, it utilizes HTTP methods such as GET, POST, PUT, and DELETE to perform operations on resources. Using the architectural style of REST API, a client sends a request to the server using one of these HTTP methods to retrieve resources. The resources on the server are identified or accessed through a Uniform Resource Locator (URL). Once the resources are identified, the server sends back responses in a standard format, typically JSON (JavaScript Object Notation)

How REST APIs work

Let's see an example that demonstrates how REST APIs work, using the illustration from the previous section explaining APIs.

Consider a scenario where a user initiates a search for a book titled “Lord of the Rings.” on your BookFinder application. The following steps will be carried out:

Step 1

Your application, also called the Client, will send a request using the HTTP method (GET) to the database service containing information about the book. The request will be sent to a specific API endpoint, represented by a URL such as “/api/books”, already designed to handle book-related requests.

A query-string format includes the search query as a parameter in the endpoint URL. Here's an example of how your request will look:

  GET /api/books?title=Lord%20of%20the%20Rings HTTP/1.1
Host: book-database-service.com

In the request, the search query parameter has been specified as key-value pairs. The key represents the parameter's name, while the value represents the search query. In this case, the key is 'title,' and the value is 'Lord of the Rings,' which has been included in the request URL. To handle spaces and special characters, the value is URL-encoded with %20. The Host indicates the actual server where the API is located.

Step 2

After receiving the request, the database service's API processes it, extracting the query parameter 'title' from the URL to determine the book name the user is searching for, such as 'Lord of the Rings'.

Step 3

Now that the API knows the name of the book the user is searching for, it interacts with the database. The API utilizes search algorithms or queries the database using a Database Query Language like SQL to find books in the database collection that match the specified title.

Here are the details of the retrieved book:

{
"title": "Lord of the Rings",
"description": "The Lord of the Rings is the saga of a group of sometimes reluctant heroes who set forth to save their world from consummate evil..",
"author": "J. R. R. Tolkien”
}

Step 4

Since the database service API uses the REST API architectural style, it returns the retrieved details to your BookFinder application in JSON format as follows:

HTTP/1.1 200 OK
Content-Type: application/json

{
"title": "Lord of the Rings",
"description": "The Lord of the Rings is the saga of a group of sometimes reluctant heroes who set forth to save their world from consummate evil..",
"author": "J. R. R. Tolkien
}

From the JSON response, you can observe the HTTP Response Status Code, which indicates the success or failure of the request. A Status Code of 200 OK signifies a successful request, prompting the return of the book information.

Additionally, take note of the included Content-Type header, which provides further information about the response, stating that the response body contains JSON-formatted data.

Step 5

This is where your BookFinder application receives the HTTP response from the database service's API and extracts the information needed to perform necessary operations or display it to users.

What is gRPC?

Google Remote Procedure Call (gRPC) is a modern framework that provides a standard for software components to communicate with each other in a distributed system. It facilitates the exchange of requests and responses.

gRPC is built on RPC (Remote Procedure Protocol), which is a high-speed communication model. It leverages HTTP/2, allowing clients to invoke functions from a remote server as if they were local. This capability enables developers to integrate services programmed in different languages.

Since gRPC is built on top of HTTP/2, it has access to certain features that offer the following benefits:

  • High Performance: gRPC utilizes Protocol Buffers, a data format for serializing structured data, which helps efficiently package and send data. Using the gRPC framework, data is transferred at high-speed thanks to the utilization of the HTTP/2 protocol. This combination of efficiency and speed in data communication leads to high-performance applications. gRPC also ensures high bidirectional streaming performance, allowing simultaneous data transmission between clients and servers.

  • Compatibility and Interoperability: The language used for defining gRPC APIs is called Protobufs, which offers several benefits in defining services and data. Using Protobufs provides a platform-independent and language-agnostic way to structure data. With Protobufs, you can write code in different programming languages, and the resulting data will seamlessly communicate with other systems. The platform-independence promotes compatibility and interoperability with other systems.

How gRPC APIs work

Let's explore the functionality of the gRPC API by using your BookFinder application as an example.

Step 1

To enable users of your application to search for a book and find the exact one they are looking for, you'll need to begin by creating a protocol buffer file named 'book.proto'. This file will include the definition of a gRPC service called BookService and its methods. One such method you'll need to create is BookSearch, which will take a search query as input and return the book details as output.

Here's an example of how your book.proto file should be structured:

syntax = "proto3";

service BookService {
rpc BookSearch(BookRequest) returns (BookResponse) {}
}

message BookRequest {
string search_query = 1;
}

message BookResponse {
string title = 1;
string description = 2;
string author = 3;
}

In the code above, the BookService is defined using the protobuf syntax of the proto3 ****version. It includes the BookSearch method, which inputs a BookRequest message and returns a BookResponse. The BookRequest message has a single field called search_query, which the user provides. The BookResponse message has fields for the book's title, description, and author.

Step 2

After defining your Protocol Buffer file, you need to generate the necessary gRPC code using the protoc compiler with the Node.js plugin (if you're using Node). This will provide you with server and client code to implement and consume the API.

Next, you'll implement the gRPC service methods you created earlier in the book.proto file. If you're using Node.js, you can utilize a library like @grpc/grpc-js, available via npm, to create the server. You can refer to the following sample to implement the server using the protocol buffer definitions:

const grpc-js = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

// Loading the protocol buffer definition
const protoFile = 'book.proto';
const packageDefinition = protoLoader.loadSync(protoFile);
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);

// Implementing the gRPC service methods
const BookService = {
BookSearch: (call, callback) => {
const searchQuery = call.request.search_query;

// Querying the book database for the book titled "Lord of the Rings"
const book = {
title: 'Lord of the Rings',
description: 'The Lord of the Rings is the saga of a group of sometimes reluctant heroes who set forth to save their world from consummate evil...',
author: 'J. R. R. Tolkien',
};

// Returning the book information as the response
callback(null, book);
},
};

// Creating a new gRPC server
const server = new grpc.Server();

// Adding the BookService implementation to the server
server.addService(protoDescriptor.BookService.service, BookService);

// Starting the server and binding it to a port
const port = 8080;
server.bind(`0.0.0.0:${port}`, grpc.ServerCredentials.createInsecure());
server.start();
console.log(`gRPC server started on port ${port}`);

The code above sets up a gRPC server to listen for incoming requests on port 8080. When a client sends a request (a search for the 'Lord of the Rings' book) to the BookSearch method, the server processes the request and responds with the 'Lord of the Rings' book information.

The proto-loader module loads and parses the protocol buffer definitions. The protoFile specifies the path to the protocol buffer definition file (book.proto). By using protoLoader.loadSync(), the contents of the protocol buffer definition file are loaded synchronously, returning a packageDefinition object.

The protoDescriptor utilizes grpc.loadPackageDefinition() to parse the loaded protocol buffer definition and generate code for gRPC communication.

Step 3

After starting the server, you can generate client code using the same protocol buffer file (book.proto). This client code will provide a client object allowing you to request the server.

On the client side, you can create a gRPC client (client-side code) and use it to send a request to the server's BookSearch method.

Here's an example of how this implementation works:

const grpc-js = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

// Loading the protocol buffer definition
const protoFile = 'book.proto';
const packageDefinition = protoLoader.loadSync(protoFile);
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);

// Creating a new gRPC client
const client = new protoDescriptor.BookService(
'localhost:8080',
grpc.credentials.createInsecure()
);

// Creating a request object
const request = { search_query: 'Lord of the Rings' };

// Sending the request to the server
client.BookSearch(request, (error, response) => {
if (error) {
console.error('Error:', error.message);
return;
}

// Handle the response
console.log('Book Information:');
console.log('Title:', response.title);
console.log('Description:', response.description);
console.log('Author:', response.author);
});

The code above creates a new gRPC client using the generated client code. The client is initialized with the server address (localhost:8080) and an insecure credential. A search query is then added to a request object with the field set to 'Lord of the Rings'. Finally, the gRPC client calls the BookSearch method, passing the request object and a callback function to handle the response. Below illustration will help you understand how both these technologies work.

REST vs gRPC

How developers can use gRPC and REST

Incorporating gRPC

  • Efficient communication between microservices: Developers can use gRPC for high-performance, low-latency communication between microservices, especially in distributed systems. For example, a microservice architecture for a real-time online gaming platform can leverage gRPC for fast, bi-directional streaming and efficient communication.
  • Mobile clients communicating with backend services: gRPC is ideal for mobile applications requiring efficient communication with backend servers. This is especially useful in scenarios where network efficiency and low latency are crucial, such as streaming data to a mobile financial trading app.

Using REST

  • Simple CRUD operations: REST is well-suited for standard web APIs that perform CRUD (Create, Read, Update, Delete) operations. For example, a web service for a book inventory system can use RESTful APIs for adding new books, retrieving book details, updating prices, or deleting old records.
  • Building public APIs for broader reach: When developing public APIs intended for a wide range of clients, including third-party developers, REST is often preferred due to its simplicity and widespread familiarity. An example is a social media platform providing a RESTful API for fetching user profiles or posting messages, allowing easy integration for a variety of external services and applications.

Security Considerations

When choosing between gRPC and REST, it's important to understand how each handles security. Both offer ways to manage authentication, authorization, and data encryption, but they do it differently.

Authentication and Authorization

gRPC

  • SSL/TLS Support: gRPC uses SSL/TLS by default to secure communication between the client and server. This means that data is encrypted and protected from eavesdropping.

  • Mutual Authentication: gRPC supports mutual SSL/TLS authentication, where both the client and server present certificates to verify each other.

  • Token-Based Authentication with Metadata: You can pass tokens or credentials using metadata in gRPC calls.

    Example in Python:

    # Client-side code
    import grpc
    from your_proto_pb2_grpc import YourServiceStub
    from your_proto_pb2 import YourRequest

    def run():
    with grpc.secure_channel('localhost:50051', grpc.ssl_channel_credentials()) as channel:
    stub = YourServiceStub(channel)
    metadata = [('authorization', 'Bearer your_token')]
    response = stub.YourMethod(YourRequest(), metadata=metadata)
    print(response)
  • Custom Authentication: You can implement custom authentication mechanisms using interceptors or middleware.

REST

  • OAuth 2.0 Integration: REST APIs often use OAuth 2.0 for authentication and authorization. This allows secure, token-based access.

    Example in Node.js with Express:

    // Server-side code
    const express = require("express");
    const app = express();
    const jwt = require("jsonwebtoken");

    app.use(express.json());

    function authenticateToken(req, res, next) {
    const authHeader = req.headers["authorization"];
    const token = authHeader && authHeader.split(" ")[1];

    if (token == null) return res.sendStatus(401);

    jwt.verify(token, "your_secret_key", (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
    });
    }

    app.get("/protected", authenticateToken, (req, res) => {
    res.json({ message: "This is protected data." });
    });

    app.listen(3000, () => console.log("Server running on port 3000"));
  • API Keys and Tokens: REST can use API keys, JSON Web Tokens (JWT), and other methods to control access.

  • HTTP Basic and Digest Authentication: These methods are simpler but should be used over HTTPS to be secure.

Data Encryption

gRPC

  • Encrypted Communication: gRPC ensures all communication is encrypted using SSL/TLS.

  • Protocol Buffers Serialization: Data is serialized using Protocol Buffers, which is efficient and not human-readable. However, this is not encryption, so sensitive data should still be sent over secure channels.

    Example of a .proto file:

    syntax = "proto3";

    service YourService {
    rpc YourMethod (YourRequest) returns (YourResponse);
    }

    message YourRequest {
    string data = 1;
    }

    message YourResponse {
    string result = 1;
    }
  • Server Implementation in Go:

    // Server-side code in Go
    import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "log"
    "net"
    )

    func main() {
    creds, err := credentials.NewServerTLSFromFile("server.crt", "server.key")
    if err != nil {
    log.Fatalf("Failed to load certificates: %v", err)
    }

    server := grpc.NewServer(grpc.Creds(creds))
    // Register your services here

    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
    log.Fatalf("Failed to listen: %v", err)
    }

    if err := server.Serve(lis); err != nil {
    log.Fatalf("Failed to serve: %v", err)
    }
    }

REST

  • HTTPS Usage: REST APIs typically use HTTPS to encrypt data between the client and server.

    // Server-side code
    const express = require("express");
    const https = require("https");
    const fs = require("fs");

    const app = express();

    // Define your routes here

    const options = {
    key: fs.readFileSync("server.key"),
    cert: fs.readFileSync("server.cert"),
    };

    https.createServer(options, app).listen(3000, () => {
    console.log("HTTPS server running on port 3000");
    });
  • Data Formats: REST often uses JSON or XML, which are human-readable. While HTTPS secures the data in transit, sensitive data should be handled carefully.

  • Additional Encryption: For extra security, you can encrypt sensitive data within the payload before sending it.

Similarities and differences between gRPC and REST

Similarities between gRPC and REST

Communication protocol: Both use HTTP protocol. • Microservices architecture: Both are suitable for building scalable microservices. • Cross-language support: Both offer support for multiple programming languages. • API design: Both are used for API development.

Differences between gRPC and REST

Protocol efficiency: gRPC uses HTTP/2 for efficient, high-speed communication, on the other hand REST typically operates on the older HTTP/1.1. • Data format: gRPC uses Protocol Buffers for compact, efficient data serialization, whereas REST commonly uses JSON. • Performance: gRPC is generally faster and more efficient due to HTTP/2 and Protocol Buffers • Caching and statelessness: REST supports caching and is stateless, which can be helpful for web applications requiring scalability and simplicity. • Browser compatibility: REST is more compatible with web browsers and simpler to implement for web applications. • Use cases: REST is ideal for CRUD operations and public-facing APIs, while gRPC excels in microservices communication and real-time data processing.

Comparison summary

The architectural style of REST API differs from that of gRPC API due to their distinct features. The table below illustrates the differences:

FeaturesREST APIgRPC API
Message FormatIt primarily uses JSON, XML, and other text-based formats.It uses Protocol Buffers as the format for data.
Caching capabilitiesIt supports caching using HTTP headers like Content-Type, Authorization, Content-Length, and others.There is no built-in caching mechanism for gRPC APIs, but external tools or libraries like gRPC client, NGINX, Redis, Memcached, etc., can be used for implementing caching.
HTTP ProtocolIt was built on HTTP/1.1, which utilizes HTTP methods (GET, POST, PUT, DELETE).It transports data over the HTTP/2 protocol, establishing bidirectional communication between the client and the server.
Generation of CodeIt facilitates the use of code generation tools and frameworks like OpenAPI and Swagger Codegen, making it easy and efficient to generate code.It uses Protobuf for code generation because it allows efficient and type-safe communication between different programming languages, ensuring interoperability.
IntegrationSince it's based on HTTP, a widely adopted standard protocol, it is supported by various frameworks like Express.js, Laravel, Ruby on Rails, Django, ASP.NET, etc.Since Protocol Buffers (protobuf) is a language-agnostic message format, it supports various programming languages including Java, C++, Go, Python, Node.js, Ruby, PHP, and more.
Cross-browser compatibilityDue to its underlying HTTP protocol, it can be accessed by any browser that supports HTTP. E.g., Google Chrome, Opera, Safari, Mozilla Firefox, etc.As gRPC is primarily designed for communication between servers and clients and not tightly coupled to browser-based applications, it is not directly dependent on browsers.

When to use REST and when to use gRPC

Use REST when you are developing web application and a more standardized approach with wide compatibility. REST is ideal for CRUD operations and when simplicity and caching are priorities. Go for gRPC if you are building application with high-performance requirements, efficient communication in microservices, and for systems where language interoperability and network efficiency are critical, such as in real-time communication.

Bonus: Migration Strategies

Transitioning from REST to gRPC can offer performance improvements and enhanced functionality. Here's a guide to help developers migrate their existing REST APIs to gRPC smoothly.

Step-by-Step Guide to Migrating from REST to gRPC

  1. Assess Your Current API Structure

    • Inventory Endpoints: List all your REST endpoints and their functionalities.
    • Identify Core Services: Determine which services will benefit most from gRPC's performance improvements.
  2. Define Protobuf Messages and Services

    • Create .proto Files: Define your data structures and services using Protocol Buffers.

      syntax = "proto3";

      package example;

      service UserService {
      rpc GetUser (GetUserRequest) returns (GetUserResponse);
      }

      message GetUserRequest {
      int32 user_id = 1;
      }

      message GetUserResponse {
      int32 user_id = 1;
      string name = 2;
      string email = 3;
      }
  3. Generate gRPC Code from Protobuf Definitions

    • Use the protoc compiler to generate server and client code in your chosen language.

      protoc --go_out=plugins=grpc:. *.proto
  4. Implement the gRPC Services

    • Server-Side Implementation: Write the server logic for your gRPC services.

      Example in Go:

      // Server-side code
      import (
      "context"
      "net"

      "google.golang.org/grpc"
      pb "path/to/your/protobuf/package"
      )

      type server struct {
      pb.UnimplementedUserServiceServer
      }

      func (s *server) GetUser(ctx context.Context, in *pb.GetUserRequest) (*pb.GetUserResponse, error) {
      // Business logic to retrieve user
      return &pb.GetUserResponse{
      UserId: in.UserId,
      Name: "John Doe",
      Email: "john.doe@example.com",
      }, nil
      }

      func main() {
      lis, _ := net.Listen("tcp", ":50051")
      s := grpc.NewServer()
      pb.RegisterUserServiceServer(s, &server{})
      s.Serve(lis)
      }
  5. Update Clients to Use gRPC

    • Client-Side Implementation: Rewrite client applications to communicate with the gRPC server.

      Example in Python:

      # Client-side code
      import grpc
      from example_pb2 import GetUserRequest
      from example_pb2_grpc import UserServiceStub

      def run():
      with grpc.insecure_channel('localhost:50051') as channel:
      stub = UserServiceStub(channel)
      response = stub.GetUser(GetUserRequest(user_id=123))
      print("User Details:", response)

      if __name__ == '__main__':
      run()

Maintain REST Endpoints During Transition

In a smooth transition from REST to gRPC, one would maintain the existing endpoint for REST and introduce the gRPC services in parallel. An API Gateway would route clients to either the REST service or the gRPC service, depending on the needs of the client. For backward compatibility, onboarding the clients one at a time with minimum disruption would be done, and API versioning handles the different stages of that process.

Unit tests and integration tests should be provided for any new gRPC services that are written. Compare their performance to REST endpoints for assurance of functionality. Deployment: With Docker, containerize your services or use similar tooling to gain a consistent approach. As changes are integrated into the CI/CD pipeline, automation will test and deploy.

Potential Pitfalls and How to Avoid Them

  • Incomplete Protobuf Definitions: Ensure all data models are accurately defined in your .proto files to prevent serialization issues.
  • Network Configuration: gRPC uses HTTP/2, so make sure your infrastructure supports it.
  • Security Concerns: Implement SSL/TLS for encrypted communication, similar to how you secure REST APIs.

Case Study: Migrating a RESTful User Service to gRPC

Let's consider a simple user service that retrieves user information.

Original REST Endpoint (Node.js with Express):

// Server-side code
const express = require("express");
const app = express();

app.get("/user/:id", (req, res) => {
const userId = req.params.id;
// Logic to get user data
res.json({
user_id: userId,
name: "John Doe",
email: "john.doe@example.com",
});
});

app.listen(3000, () => console.log("REST API running on port 3000"));

Migrated gRPC Service (Node.js):

  1. Define the .proto File

    syntax = "proto3";

    package example;

    service UserService {
    rpc GetUser (GetUserRequest) returns (GetUserResponse);
    }

    message GetUserRequest {
    int32 user_id = 1;
    }

    message GetUserResponse {
    int32 user_id = 1;
    string name = 2;
    string email = 3;
    }
  2. Implement the Server

    // Server-side code
    const grpc = require("@grpc/grpc-js");
    const protoLoader = require("@grpc/proto-loader");

    const packageDefinition = protoLoader.loadSync("example.proto");
    const proto = grpc.loadPackageDefinition(packageDefinition).example;

    function getUser(call, callback) {
    const userId = call.request.user_id;
    // Logic to get user data
    callback(null, {
    user_id: userId,
    name: "John Doe",
    email: "john.doe@example.com",
    });
    }

    const server = new grpc.Server();
    server.addService(proto.UserService.service, { GetUser: getUser });
    server.bindAsync(
    "0.0.0.0:50051",
    grpc.ServerCredentials.createInsecure(),
    () => {
    server.start();
    console.log("gRPC server running on port 50051");
    },
    );
  3. Implement the Client

    // Client-side code
    const grpc = require("@grpc/grpc-js");
    const protoLoader = require("@grpc/proto-loader");

    const packageDefinition = protoLoader.loadSync("example.proto");
    const proto = grpc.loadPackageDefinition(packageDefinition).example;

    const client = new proto.UserService(
    "localhost:50051",
    grpc.credentials.createInsecure(),
    );

    client.GetUser({ user_id: 123 }, (error, response) => {
    if (error) {
    console.error("Error:", error);
    } else {
    console.log("User Details:", response);
    }
    });

Tools to Assist Migration

  • gRPC-Gateway: Allows you to expose gRPC services as REST endpoints, helping in gradual migration.

    Example usage in Go:

    // Install gRPC-Gateway
    go get -u github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway

    // Generate reverse-proxy
    protoc -I . example.proto --grpc-gateway_out=.

    // Run the gateway server
  • API Gateways: Tools like Envoy or Kong can route traffic between REST and gRPC services.

Benefits Achieved After Migration

  • Improved Performance: Lower latency and higher throughput with gRPC.
  • Efficient Data Transfer: Smaller payload sizes with Protocol Buffers.
  • Bi-Directional Streaming: Enhanced capabilities not available in REST.

Conclusion

Finally, you've reached the end of the article, where you learned about the architectural style of REST APIs and gRPC APIs, including how they work and the differences between their distinctive features.

Resources

You may find the following resources useful: