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.
Outline
- Introduction
- Brief overview of REST APIs
- Importance in modern web development
- Python FastAPI
- Overview and setup
- Pros and Cons
- Performance benchmarks
- Best practices
- Go Lang Gin
- Overview and setup
- Pros and Cons
- Performance benchmarks
- Best practices
- Java Spring Boot
- Overview and setup
- Pros and Cons
- Performance benchmarks
- Best practices
- Comparative Analysis
- Table comparing features and benchmarks
- Conclusion
- Summary of findings
- Recommendations based on use cases
- 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.
- 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.
- 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.
- 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.
- 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.
- 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 Stats | Avg | Stdev | Max | +/- Stdev |
---|---|---|---|---|
Latency | 13.96ms | 23.11ms | 199.04ms | 87.12% |
Req/Sec | 3.68k | 1.45k | 8.08k | 68.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 Stats | Avg | Stdev | Max | +/- Stdev |
---|---|---|---|---|
Latency | 15.44ms | 26.41ms | 279.96ms | 87.07% |
Req/Sec | 8.74k | 5.79k | 18.58k | 56.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 Stats | Avg | Stdev | Max | +/- Stdev |
---|---|---|---|---|
Latency | 8.16ms | 10.14ms | 87.11ms | 85.63% |
Req/Sec | 2.04k | 124.28 | 2.77k | 67.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 Stats | Avg | Stdev | Max | +/- Stdev |
---|---|---|---|---|
Latency | 23.68ms | 17.69ms | 279.40ms | 75.35% |
Req/Sec | 458.42 | 141.40 | 712.00 | 66.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:
- HTTP GET endpoint that returns "Hello World" (metrics shared above).
- IO-bound file read endpoint that opens a text file (.txt), reads the content, and returns the content (metrics shared below).
- 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.
- 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")));
}
Framework | Duration | Threads | Connections | Avg Latency | Stdev Latency | Max Latency | +/- Stdev | Req/Sec | Stdev Req/Sec | Max Req/Sec | +/- Stdev Req | Total Requests | Total Data Read | Requests/sec | Transfer/sec |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
FastAPI | 30s | 2 | 20 | 18.12ms | 2.47ms | 50.27ms | 96.26% | 554.72 | 47.37 | 606.00 | 71.50% | 33154 | 4.90MB | 1103.81 | 167.09KB |
Gin | 30s | 2 | 20 | 13.45ms | 22.25ms | 200.97ms | 86.84% | 6.47k | 4.06k | 13.85k | 59.83% | 383852 | 48.32MB | 12782.48 | 1.61MB |
Spring | 30s | 2 | 20 | 9.65ms | 11.89ms | 85.79ms | 83.59% | 1.90k | 145.85 | 2.94k | 69.62% | 113464 | 13.98MB | 3772.91 | 475.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"));
}
Framework | Duration | Threads | Connections | Avg Latency | Stdev Latency | Max Latency | +/- Stdev | Req/Sec | Stdev Req/Sec | Max Req/Sec | +/- Stdev Req | Total Requests | Total Data Read | Requests/sec | Transfer/sec |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
FastAPI | 30s | 2 | 20 | 344.67ms | 29.63ms | 472.45ms | 80.46% | 31.04 | 17.84 | 90.00 | 58.53% | 1730 | 261.87KB | 57.60 | 8.72KB |
Gin | 30s | 2 | 20 | 16.83ms | 11.87ms | 173.57ms | 97.15% | 641.95 | 86.70 | 777.00 | 84.92% | 38223 | 5.58MB | 1273.03 | 190.21KB |
Spring | 30s | 2 | 20 | 192.86ms | 244.06ms | 1.90s | 93.84% | 72.64 | 30.26 | 151.00 | 67.33% | 4079 | 618.02KB | 135.72 | 20.56KB |
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));
}
Framework | Duration | Threads | Connections | Avg Latency | Stdev Latency | Max Latency | +/- Stdev | Req/Sec | Stdev Req/Sec | Max Req/Sec | +/- Stdev Req | Total Requests | Total Data Read | Requests/sec | Transfer/sec |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
FastAPI | 30s | 2 | 20 | 87.97ms | 3.68ms | 120.86ms | 90.42% | 114.01 | 13.58 | 141.00 | 44.17% | 6815 | 0.91MB | 226.94 | 31.03KB |
Gin | 30s | 2 | 20 | 29.18ms | 49.64ms | 424.85ms | 87.12% | 4.73k | 3.65k | 9.44k | 39.36% | 267938 | 35.26MB | 8923.65 | 1.17MB |
Spring | 30s | 2 | 20 | 10.23ms | 11.93ms | 109.04ms | 84.00% | 1.56k | 128.37 | 2.17k | 68.90% | 93048 | 12.35MB | 3096.31 | 420.89KB |
Understanding the Metrics
- 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.
- 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.
- 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.