Building a REST API: Python FastAPI vs Go Lang Gin vs Java Spring Boot

Welcome to my guide on building a REST API using three popular frameworks: Python FastAPI, Go Lang Gin, and Java Spring Boot. This post is designed to help technical and non-technical readers understand the best practices in API development across these languages. We'll explore each framework, discuss their pros and cons, and provide benchmark comparisons of API response times. Plus, you'll find links to GitHub repositories for each framework, serving as ready-to-use templates for your projects.

🚧
Note: this post's content will be updated with more information, code, and advice. Stay tuned!

Outline

  1. Introduction
    • Brief overview of REST APIs
    • Importance in modern web development
  2. Python FastAPI
    • Overview and setup
    • Pros and Cons
    • Performance benchmarks
    • Best practices
  3. Go Lang Gin
    • Overview and setup
    • Pros and Cons
    • Performance benchmarks
    • Best practices
  4. Java Spring Boot
    • Overview and setup
    • Pros and Cons
    • Performance benchmarks
    • Best practices
  5. Comparative Analysis
    • Table comparing features and benchmarks
  6. Conclusion
    • Summary of findings
    • Recommendations based on use cases
  7. References

1. Introduction

Brief Overview of REST APIs

REST, or Representational State Transfer, is an architectural style for designing networked applications. It relies on a stateless, client-server communication model, considered a standard for designing networked applications. RESTful APIs, or REST APIs, are based on this architecture and are used to enable interactions between clients and servers.

A REST API defines a set of functions where developers can perform requests and receive responses. In a RESTful system, these operations typically involve the standard HTTP methods such as GET, POST, PUT, DELETE, and PATCH.

  • GET is used to retrieve a resource.
  • POST is used to create a new resource.
  • PUT and PATCH are used to update existing resources, with PATCH applying a partial update.
  • DELETE is used to remove resources.

Each resource in a REST API is identified by URIs (Uniform Resource Identifiers) and is represented in a format such as JSON or XML. A key feature of REST APIs is statelessness, meaning that each request from a client to a server must contain all the information needed to understand and complete the request. The server does not store any state about the client session on the server side.

Importance in Modern Web Development

REST APIs have become fundamental in modern web development due to their simplicity, scalability, and flexibility. They allow different applications, systems, or services to communicate with each other regardless of their underlying technology or platform. This interoperability is crucial in today's diverse technological landscape.

  1. Platform and Language Agnosticism: REST APIs can be consumed by any client that understands HTTP, making them accessible from virtually any programming language and platform. This universality facilitates the integration of various technologies within a project.
  2. Scalability and Performance: Due to their stateless nature, REST APIs can handle multiple requests more efficiently, making them highly scalable. This is essential for developing web applications that need to support a large number of users or requests.
  3. Ease of Use and Flexibility: REST APIs are easy to understand and use, which speeds up development and reduces the learning curve for new developers. They also offer flexibility in terms of data formats and resource representations.
  4. Microservices Architecture: REST APIs are a perfect fit for microservices architecture – a design approach to build a single application as a suite of small services. Each service runs in its own process and communicates with lightweight mechanisms, often an HTTP-based API.
  5. Community and Tooling Support: The widespread adoption of REST APIs has led to a rich ecosystem of tools, libraries, and community knowledge. This ecosystem provides robust support for developing, testing,Python-type and maintaining RESTful services.

REST APIs have become a cornerstone of modern web development, enabling seamless communication between software applications. In this post, we'll explore how to build a REST API using three different frameworks: Python FastAPI, Go Lang Gin, and Java Spring Boot.

2. Python FastAPI

Overview and Setup

FastAPI is a modern, fast (high-performance) web framework for building APIs with Python 3.7+ based on standard Python-type hints. FastAPI is renowned for its speed and ease of use, particularly due to its asynchronous support and automatic data validation. It's built on Starlette for the web and Pydantic for the data parts.

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello World"}

Pros and Cons

Pros:

  • Fast performance
  • Easy to use and learn
  • Robust integration with Python async features
  • Automatic Swagger UI generation for testing and exploring your API
  • A dependency injection system simplifies code maintenance and testing

Cons:

  • Relatively new, smaller community
  • Limited resources compared to older frameworks
  • Asynchronous programming can be complex for beginners
  • Limited examples and resources for very advanced use cases

Performance Benchmarks

Thanks to Starlette and Pydantic, FastAPI is one of the fastest frameworks for building APIs in Python.

Hello World (Defaults - Logging Enabled)

Running 30s test - 2 threads and 20 connections

Thread Stats Avg Stdev Max +/- Stdev
Latency 15.99ms 0.86ms 33.04ms 96.87%
Req/Sec 627.85 32.15 760.00 78.33%

Summary:

  • 37,507 requests in 30s, 5.44MB read
  • Requests/sec: 1,249.50
  • Transfer/sec: 185.48KB

Hello World (Logging Disabled)

Running 30s test - 2 threads and 20 connections

Metric Avg Stdev Max +/- Stdev
Latency 12.76ms 709.11us 24.31ms 97.35%
Req/Sec 786.86 26.10 0.88k 79.50%

Summary:

  • 47,008 requests in 30.02s, 6.81MB read
  • Requests/sec: 1,566.03
  • Transfer/sec: 232.46KB

Best Practices

  • Use Pydantic models for data validation
  • Leverage asynchronous programming for IO-bound operations
  • Utilize FastAPI's dependency injection for reusable components
  • Utilize production configurations (impacts overall performance based on server settings)

3. Go Lang Gin

Overview and Setup

Gin is a web framework written in Go (Golang). It features a Martini-like API using httprouter. Gin is known for its high performance and efficiency, making it a popular choice for building lightweight but powerful web applications and microservices in Go.

package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "Hello World"})
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

Pros and Cons

Pros:

  • Exceptional performance
  • Simple and efficient routing
  • Strong support for middleware
  • Minimalist framework, leading to less boilerplate code
  • Middleware support allows for easy logging, CORS, etc

Cons:

  • Less idiomatic Go code
  • Smaller community than other Go web frameworks
  • Error handling in Gin can be less intuitive compared to other frameworks

Performance Benchmarks

Gin is one of the fastest web frameworks for Go, often used in scenarios where performance is a critical factor.

Hello World (Defaults - Logging Enabled)

Running 30s test - 2 threads and 20 connections

Thread StatsAvgStdevMax+/- Stdev
Latency13.96ms23.11ms199.04ms87.12%
Req/Sec3.68k1.45k8.08k68.39%

Summary:

  • 219,053 requests in 30.02s, 31.34MB read
  • Requests/sec: 7,296.04
  • Transfer/sec: 1.04MB

Hello World (Logging Disabled)

Running 30s test - 2 threads and 20 connections

Thread StatsAvgStdevMax+/- Stdev
Latency15.44ms26.41ms279.96ms87.07%
Req/Sec8.74k5.79k18.58k56.83%

Summary:

  • 519,153 requests in 30.02s, 74.27MB read
  • Requests/sec: 17,294.79
  • Transfer/sec: 2.47MB

Best Practices

  • Utilize middleware for logging, recovery, and more
  • Keep your code idiomatic to Go for maintainability
  • Properly structure your code to avoid common pitfalls in routing and middleware usage
  • Use Go routines wisely for concurrent tasks to maximize performance
  • Utilize production configurations (impacts overall performance based on server settings)

4. Java Spring Boot

Overview and Setup

Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can "just run." It simplifies the development of Spring applications by offering a range of out-of-the-box features like embedded servers, metrics, health checks, and externalized configuration.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }

    @RestController
    public class HelloController {

        @GetMapping("/")
        public String index() {
            return "{\"message\":\"Hello World\"}";
        }
    }
}

Pros and Cons

Pros:

  • Comprehensive documentation and community support
  • High scalability and flexibility
  • Rich set of features and integrations
  • Vast ecosystem with support for various data formats and databases

Cons:

  • Steeper learning curve
  • More boilerplate code compared to other frameworks
  • Longer startup time compared to other frameworks

Performance Benchmarks

While not the fastest in raw performance, Spring Boot offers a balanced mix of speed and functionality, especially suitable for enterprise-level applications.

Hello World (Defaults - Logging Disabled)

Running 30s test - 2 threads and 20 connections

Thread StatsAvgStdevMax+/- Stdev
Latency8.16ms10.14ms87.11ms85.63%
Req/Sec2.04k124.282.77k67.89%

Summary:

  • 121,712 requests in 30.07s, 17.67MB read
  • Requests/sec: 4,047.42
  • Transfer/sec: 601.56KB

Hello World (Logging Enabled - Logging overhead can impact performance)

Running 30s test - 2 threads and 20 connections

Thread StatsAvgStdevMax+/- Stdev
Latency23.68ms17.69ms279.40ms75.35%
Req/Sec458.42141.40712.0066.00%

Summary:

  • 27,451 requests in 30.08s, 3.98MB read
  • Requests/sec: 912.67
  • Transfer/sec: 135.64KB

Best Practices

  • Follow the Spring framework's best practices for configuration and dependency injection
  • Keep your codebase clean and modular
  • Regularly review dependencies to keep your Spring Boot application lightweight
  • Embrace Spring Boot's auto-configuration, which defaults to Production configurations, but understand its inherent limits and when to override settings. For example, by default, Spring is intended to be optimized for Production use, whereas other frameworks may require configuration to become optimal.

5. Comparative Analysis

Benchmark tests can vary significantly depending on several factors, including the specific environment, hardware specifications, software versions, and configurations used during testing. Although every effort was made to create a level playing field for all three frameworks by disabling logging and using the same EC2 instance type, slight variations in results are to be expected. These benchmarks aim to provide a general comparison rather than definitive performance metrics, as real-world applications may experience different performance characteristics based on their unique requirements and configurations.

The goal was to evaluate the performance of all three frameworks under the following four scenarios:

  1. HTTP GET endpoint that returns "Hello World" (metrics shared above).
  2. IO-bound file read endpoint that opens a text file (.txt), reads the content, and returns the content (metrics shared below).
  3. IO-bound data fetch endpoint that makes a request to an external API (jsonplaceholder.typicode.com), parses/unmarshals the data to a dict/interface/object, and returns the value of a single key.
  4. CPU-bound endpoint that returns a Fibonacci calculation (metrics shared below).

Using the WRK library:

The benchmarks in this post were conducted using the WRK library, a modern HTTP benchmarking tool capable of generating significant load when testing web servers. WRK is highly configurable, allowing users to specify the number of threads, connections, and duration for each test. The following is a brief overview of the WRK command used for these benchmarks:

  • -t: Specifies the number of threads to use.
  • -c: Specifies the number of connections to keep open.
  • -d: Specifies the duration of the test.

For example, the command wrk -t2 -c20 -d30s http://localhost:<port>/<endpoint> runs a benchmark test against the specified URL with 2 threads, 20 connections, and a duration of 30 seconds.

Reading a file from disk and returning the content:

# FASTAPI
@app.get("/readfile")
async def read_file():
  async with aiofiles.open('sample.txt', mode='r') as f:
      content = await f.read()
  return {"content": content}
// GIN
r.GET("/readfile", func(c *gin.Context) {
  content, err := ioutil.ReadFile("sample.txt")
  if err != nil {
      c.String(http.StatusInternalServerError, err.Error())
      return
  }
  c.String(http.StatusOK, string(content))
})
// SPRING BOOT
@GetMapping("/readfile")
public String readFile() throws IOException {
    return new String(Files.readAllBytes(Paths.get("sample.txt")));
}
FrameworkDurationThreadsConnectionsAvg LatencyStdev LatencyMax Latency+/- StdevReq/SecStdev Req/SecMax Req/Sec+/- Stdev ReqTotal RequestsTotal Data ReadRequests/secTransfer/sec
FastAPI30s22018.12ms2.47ms50.27ms96.26%554.7247.37606.0071.50%331544.90MB1103.81167.09KB
Gin30s22013.45ms22.25ms200.97ms86.84%6.47k4.06k13.85k59.83%38385248.32MB12782.481.61MB
Spring30s2209.65ms11.89ms85.79ms83.59%1.90k145.852.94k69.62%11346413.98MB3772.91475.99KB

Fetching external API data and returning a single value:

# FASTAPI
@app.get("/fetchdata")
async def fetch_data():
  async with httpx.AsyncClient() as client:
      response = await client.get(
          'https://jsonplaceholder.typicode.com/todos/1'
          )
  data = response.json()
  return {"title": data["title"]}
// GIN
r.GET("/fetchdata", func(c *gin.Context) {
    resp, err := http.Get("https://jsonplaceholder.typicode.com/todos/1")
    if err != nil {
        c.String(http.StatusInternalServerError, err.Error())
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        c.String(http.StatusInternalServerError, err.Error())
        return
    }
    var data map[string]interface{}
    if err := json.Unmarshal(body, &data); err != nil {
        c.String(http.StatusInternalServerError, err.Error())
        return
    }
    c.JSON(http.StatusOK, gin.H{"title": data["title"]})
})
// SPRING BOOT
@GetMapping("/fetchdata")
public Map<String, String> fetchData() {
    RestTemplate restTemplate = new RestTemplate();
    String url = "https://jsonplaceholder.typicode.com/todos/1";
    Map<String, Object> response = restTemplate.getForObject(url, Map.class);
    return Collections.singletonMap("title", (String) response.get("title"));
}
FrameworkDurationThreadsConnectionsAvg LatencyStdev LatencyMax Latency+/- StdevReq/SecStdev Req/SecMax Req/Sec+/- Stdev ReqTotal RequestsTotal Data ReadRequests/secTransfer/sec
FastAPI30s220344.67ms29.63ms472.45ms80.46%31.0417.8490.0058.53%1730261.87KB57.608.72KB
Gin30s22016.83ms11.87ms173.57ms97.15%641.9586.70777.0084.92%382235.58MB1273.03190.21KB
Spring30s22016.83ms11.87ms173.57ms97.15%641.9586.70777.0084.92%382235.58MB1273.03190.21KB

Calculating Fibonacci:

# FASTAPI
def fibonacci(n: int) -> int:
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)

@app.get("/fibonacci")
async def calculate_fibonacci(n: int):
    return {"result": fibonacci(n)}
// GIN
func fibonacci(n int) int {
    if n <= 0 {
        return 0
    } else if n == 1 {
        return 1
    } else {
        return fibonacci(n-1) + fibonacci(n-2)
    }
}
//...
r.GET("/fibonacci", func(c *gin.Context) {
    n, _ := strconv.Atoi(c.Query("n"))
    result := fibonacci(n)
    c.JSON(http.StatusOK, gin.H{"result": result})
})
// SPRING BOOT
public int fibonacci(int n) {
    if (n <= 0) {
        return 0;
    } else if (n == 1) {
        return 1;
    } else {
        return fibonacci(n-1) + fibonacci(n-2);
    }
}
//...
@GetMapping("/fibonacci")
public Map<String, Integer> calculateFibonacci(@RequestParam int n) {
    return Collections.singletonMap("result", fibonacci(n));
}
FrameworkDurationThreadsConnectionsAvg LatencyStdev LatencyMax Latency+/- StdevReq/SecStdev Req/SecMax Req/Sec+/- Stdev ReqTotal RequestsTotal Data ReadRequests/secTransfer/sec
FastAPI30s22087.97ms3.68ms120.86ms90.42%114.0113.58141.0044.17%68150.91MB226.9431.03KB
Gin30s22029.18ms49.64ms424.85ms87.12%4.73k3.65k9.44k39.36%26793835.26MB8923.651.17MB
Spring30s22010.23ms11.93ms109.04ms84.00%1.56k128.372.17k68.90%9304812.35MB3096.31420.89KB

Understanding the Metrics

  1. Requests per Second (Req/Sec): This metric indicates the number of HTTP requests the server can handle per second. A higher value suggests the server can process more requests in a given time frame, implying better performance under load.
  2. Transfer Rate (Transfer/sec): This measures the amount of data transferred per second. A higher transfer rate indicates the server handles a larger volume of data efficiently.
  3. Latency: Although latency (the time taken to process a single request) is important, it needs to be considered alongside Req/Sec and Transfer/sec to get a full picture of performance.

Why Gin Shows Better Performance

  • Higher Requests per Second: Gin handles significantly more requests per second than FastAPI and Spring Boot. This indicates that Gin is more efficient at processing many requests in a given timeframe.
  • Higher Transfer Rate: Gin also transfers more data per second compared to FastAPI and Spring Boot, showing it can handle a higher data throughput.

Single EC2 Instance Considerations

All tests were conducted on a single EC2 instance (t2.micro), which may not fully represent the performance of these frameworks in a more distributed and scaled environment (production grade). Network latency, CPU, and memory limitations of a single instance can affect the results. Therefore, while these benchmarks provide some insights, they should be interpreted with an understanding of these constraints.

6. Conclusion

In conclusion, each framework has its strengths and weaknesses. Your choice should depend on your specific needs, team expertise, and the nature of the project. FastAPI is excellent for rapid development with Python, Gin for high-performance APIs in Go, and Spring Boot for robust, feature-rich applications in Java.

7. References


Note: Benchmark tests can vary significantly based on the environment, versions of the frameworks, and other external factors. While I aimed to make the testing conditions as even as possible, results can fluctuate due to differences in configurations and system resources. Therefore, these results should be viewed as indicative rather than definitive, and it's recommended to perform your benchmarks in a controlled environment that mirrors your production setup.

You've successfully subscribed to Amitk.io
Great! Next, complete checkout for full access to Amitk.io
Welcome back! You've successfully signed in.
Unable to sign you in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.