Skip to content

PostgreSQL REST API Benchmark: 15 Frameworks Compared

Benchmark · Performance · December 2025


How fast can different frameworks serve data from PostgreSQL? We put 15 popular REST API frameworks to the test, measuring their ability to execute a PostgreSQL function and return JSON results under various load conditions.

What We Tested

All frameworks executed the same PostgreSQL function that returns a configurable number of JSON records. This isolates the framework overhead from database performance - every framework runs the identical query against the same PostgreSQL instance.

Test Setup:

  • PostgreSQL Function: Returns 1, 10, 100, or 500 JSON records (view source)
  • Load Tool: k6 load testing framework
  • Duration: 60 seconds per test
  • Concurrency: 1, 50, and 100 virtual users (VUs)
  • Environment: All services running in Docker containers on the same host
  • Hardware: Hetzner Cloud CCX33 (General Purpose, x86 AMD) - 8 dedicated vCPUs, 32 GB RAM, 240 GB SSD, 30 TB traffic

Frameworks Tested:

FrameworkLanguageVersion
NpgsqlRest (JIT).NET3.2.2
NpgsqlRest (AOT).NET3.2.2, 2.36.2
PostgRESTHaskell12.2.8
Go (net/http + pgx)Go1.24
Rust (Actix + tokio-postgres)Rust1.83.0
Spring BootJava 244.0.1
.NET Minimal API + Dapper.NET 10-
.NET Minimal API + EF Core.NET 9/10-
FastifyNode.js5.6.2
BunBun1.1.42
SwoolePHP8.4.0
FastAPIPython0.127.1
DjangoPython6.0

Key Findings

NpgsqlRest JIT Dominates High-Concurrency Scenarios

At 100 concurrent users with minimal payload (1 record), NpgsqlRest JIT achieves 5,177 requests per second - nearly 35% faster than the second-place Swoole PHP and over 50% faster than Rust. This demonstrates the efficiency of NpgsqlRest's direct PostgreSQL-to-HTTP pipeline.

The Top Performers

TierFrameworksPeak Performance
EliteNpgsqlRest JIT5,177 req/s
TopSwoole PHP, Rust, Fastify3,000-3,800 req/s
HighGo, Spring Boot, Bun, NpgsqlRest AOT, .NET Dapper2,500-2,700 req/s
Mid.NET EF Core, Django1,600-2,200 req/s
LowerFastAPI, PostgREST800-1,300 req/s

Performance Scales Differently

Not all frameworks scale equally when concurrency increases:

  • NpgsqlRest JIT improves from 601 req/s (1 VU) to 5,177 req/s (100 VU) - an 8.6x improvement
  • Swoole PHP scales from 379 req/s to 3,855 req/s - a 10x improvement
  • PostgREST actually degrades under load: 385 req/s (1 VU) drops to 843 req/s (100 VU) - only 2.2x improvement

Larger Payloads Level the Playing Field

With 500 records returned per request, the performance gap narrows significantly as database I/O and JSON serialization dominate:

Framework500 Records @ 100 VULatency
NpgsqlRest JIT100.90 req/s495ms
Django64.22 req/s779ms
Swoole PHP62.99 req/s793ms
Rust54.94 req/s911ms
FastAPI17.99 req/s2,796ms

Even here, NpgsqlRest maintains a significant lead, processing 60% more requests than the second-place finisher.

Python Frameworks Struggle Under Load

Both FastAPI and Django show concerning behavior under concurrent load:

  • FastAPI at 100 VU with 500 records: 2.8 second average latency
  • Django performs better with larger payloads but still lags in low-latency scenarios

JIT vs AOT: When to Use Each

NpgsqlRest offers both JIT (Just-In-Time compiled) and AOT (Ahead-Of-Time compiled) versions:

  • JIT version consistently outperforms AOT by 50-100% in high-concurrency scenarios
  • AOT version has faster cold-start times, smaller memory footprint, and significantly smaller Docker image size (172 MB vs 426 MB for JIT)
  • For sustained high-throughput workloads, JIT is the clear winner
  • For containerized deployments where image size matters (serverless, edge), AOT may be preferred

Why NpgsqlRest Performs So Well

NpgsqlRest's architecture eliminates multiple layers that other frameworks require:

  1. No ORM overhead - Direct PostgreSQL protocol communication via Npgsql
  2. No routing framework - Endpoints derived directly from database metadata
  3. No serialization layer - PostgreSQL's native JSON functions handle serialization
  4. Minimal memory allocation - Optimized hot paths using ValueTask and buffer pooling
  5. Connection pooling - Efficient connection reuse via Npgsql's built-in pooling

Traditional frameworks must: parse routes, validate input, map to models, execute queries, map results, serialize to JSON, and format responses. NpgsqlRest collapses this into: receive request, execute function, stream response.

Important Note: JSON and Array Type Handling

When interpreting these benchmark results, it's important to understand that not all frameworks return identical JSON responses. The differences lie in how each framework handles PostgreSQL's JSON, JSONB, and array types.

Here's a summary of actual test results from each framework:

Frameworkjsonjsonbint[]text[]
NpgsqlRest (JIT/AOT)
.NET EF Core / Dapper
Rust
Fastify
Django
Go
FastAPI
PostgREST⚠️⚠️
Bun⚠️⚠️
Spring Boot⚠️⚠️
Swoole PHP

✅ = Properly parsed as native JSON/array ❌ = Returns raw PostgreSQL text format (string) ⚠️ = Unusual format (wrapped in metadata or array)

Correct handling (NpgsqlRest, Rust, Fastify, .NET):

json
{
  "json_val": {"i": 1, "key": "value"},
  "jsonb_val": {"key": "value", "row": 1},
  "int_array_val": [1, 2, 3, 1],
  "text_array_val": ["a", "b", "c", "test1"]
}

Raw PostgreSQL format (Swoole PHP):

json
{
  "json_val": "{\"i\": 1, \"key\": \"value\"}",
  "jsonb_val": "{\"key\": \"value\", \"row\": 1}",
  "int_array_val": "{1,2,3,1}",
  "text_array_val": "{a,b,c,test1}"
}

Partial handling (Go, FastAPI - arrays work, JSON/JSONB returned as strings):

json
{
  "json_val": "{\"i\": 1, \"key\": \"value\"}",
  "jsonb_val": "{\"key\": \"value\", \"row\": 1}",
  "int_array_val": [1, 2, 3, 1],
  "text_array_val": ["a", "b", "c", "test1"]
}

Unusual wrapping (Spring Boot - wraps JSON in type metadata):

json
{
  "json_val": {"null": false, "type": "json", "value": "{\"i\": 1, \"key\": \"value\"}"},
  "jsonb_val": {"null": false, "type": "jsonb", "value": "{\"key\": \"value\", \"row\": 1}"}
}

This means frameworks like Swoole PHP's impressive performance numbers come with a caveat: properly parsing JSON and array fields would require an additional processing step on the client, which would impact overall application performance. The .NET services in this benchmark use a custom RawJsonConverter to handle this correctly.

This difference should be considered when evaluating which framework best fits your use case - raw throughput vs. correct type handling out of the box.

Conclusion

For PostgreSQL-backed REST APIs where performance matters, NpgsqlRest JIT delivers exceptional throughput - often 2-5x faster than traditional frameworks. The "database as API" approach isn't just about developer productivity; it's a fundamentally more efficient architecture.

Beyond performance, the development effort difference is striking. Here's the code required for each framework in this benchmark:

FrameworkLines of Code
NpgsqlRest22 (config only)
Fastify46
Swoole PHP84
Go129
Rust142

NpgsqlRest requires zero application code - just a 22-line JSON configuration file. The framework automatically discovers your PostgreSQL functions and exposes them as REST endpoints. Compare this to Go (129 lines) or Rust (142 lines) where you must manually define routes, handle parameters, execute queries, and serialize responses.

This means NpgsqlRest delivers the best performance with the least code - a rare combination that eliminates entire categories of bugs and maintenance burden.

The benchmark source code and detailed results are available in the pg_function_load_tests repository.


Full Benchmark Results

Results are grouped by concurrency level and payload size, sorted by requests per second (highest first).

1 Virtual User, 1 Record

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
npgsqlrest-jit-v3.2.2601.37/s1.65ms36,083summarysource
go-app-v1.24434.56/s2.29ms26,074summarysource
postgrest-v12.2.8385.89/s2.58ms23,155summarysource
swoole-php-app-v8.4.0379.12/s2.62ms22,748summarysource
npgsqlrest-aot-v2.36.2375.83/s2.65ms22,551summarysource
fastify-app-v5.6.2373.30/s2.66ms22,399summarysource
bun-app-v1.1.42360.33/s2.76ms21,620summarysource
npgsqlrest-aot-v3.2.2358.91/s2.77ms21,535summarysource
java24-spring-boot-v4.0.1357.59/s2.78ms21,456summarysource
fastapi-app-v0.127.1357.20/s2.78ms21,432summarysource
rust-app-v1.83.0348.83/s2.85ms20,930summarysource
net10-minapi-dapper-jit334.92/s2.97ms20,096summarysource
net9-minapi-ef-jit291.26/s3.42ms17,477summarysource
net10-minapi-ef-jit291.09/s3.42ms17,466summarysource
django-app-v6.0200.85/s4.96ms12,051summarysource

1 Virtual User, 10 Records

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
npgsqlrest-jit-v3.2.2281.62/s3.54ms16,898summarysource
swoole-php-app-v8.4.0236.71/s4.21ms14,203summarysource
go-app-v1.24226.25/s4.40ms13,576summarysource
npgsqlrest-aot-v2.36.2208.80/s4.77ms12,528summarysource
npgsqlrest-aot-v3.2.2204.71/s4.87ms12,283summarysource
fastify-app-v5.6.2203.79/s4.89ms12,228summarysource
rust-app-v1.83.0203.19/s4.91ms12,192summarysource
postgrest-v12.2.8196.37/s5.08ms11,783summarysource
net10-minapi-dapper-jit195.52/s5.10ms11,732summarysource
java24-spring-boot-v4.0.1191.65/s5.20ms11,499summarysource
bun-app-v1.1.42185.44/s5.38ms11,127summarysource
net10-minapi-ef-jit179.23/s5.56ms10,754summarysource
net9-minapi-ef-jit173.02/s5.76ms10,382summarysource
fastapi-app-v0.127.1164.15/s6.08ms9,849summarysource
django-app-v6.0135.13/s7.38ms8,108summarysource

1 Virtual User, 100 Records

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
swoole-php-app-v8.4.053.55/s18.66ms3,213summarysource
npgsqlrest-jit-v3.2.251.06/s19.57ms3,064summarysource
go-app-v1.2448.71/s20.51ms2,923summarysource
npgsqlrest-aot-v2.36.246.92/s21.29ms2,816summarysource
npgsqlrest-aot-v3.2.246.14/s21.65ms2,769summarysource
rust-app-v1.83.044.81/s22.29ms2,689summarysource
fastify-app-v5.6.241.95/s23.82ms2,517summarysource
bun-app-v1.1.4239.52/s25.28ms2,372summarysource
postgrest-v12.2.838.50/s25.95ms2,311summarysource
net10-minapi-dapper-jit38.21/s26.15ms2,293summarysource
net10-minapi-ef-jit38.09/s26.23ms2,286summarysource
net9-minapi-ef-jit37.64/s26.55ms2,259summarysource
django-app-v6.035.22/s28.37ms2,114summarysource
java24-spring-boot-v4.0.132.62/s30.63ms1,958summarysource
fastapi-app-v0.127.127.60/s36.21ms1,657summarysource

1 Virtual User, 500 Records

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
swoole-php-app-v8.4.012.31/s81.22ms739summarysource
npgsqlrest-jit-v3.2.211.48/s87.07ms690summarysource
go-app-v1.2410.87/s91.99ms653summarysource
npgsqlrest-aot-v3.2.210.46/s95.54ms628summarysource
npgsqlrest-aot-v2.36.210.39/s96.18ms624summarysource
rust-app-v1.83.010.22/s97.84ms614summarysource
bun-app-v1.1.429.54/s104.81ms573summarysource
fastify-app-v5.6.29.35/s106.89ms562summarysource
postgrest-v12.2.88.76/s114.07ms526summarysource
net10-minapi-ef-jit8.62/s115.92ms518summarysource
net9-minapi-ef-jit8.56/s116.74ms514summarysource
net10-minapi-dapper-jit8.47/s118.01ms509summarysource
django-app-v6.08.42/s118.80ms505summarysource
java24-spring-boot-v4.0.17.27/s137.44ms437summarysource
fastapi-app-v0.127.16.36/s157.25ms382summarysource

50 Virtual Users, 1 Record

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
swoole-php-app-v8.4.03,596.04/s6.94ms215,868summarysource
npgsqlrest-jit-v3.2.23,298.63/s7.56ms197,959summarysource
rust-app-v1.83.03,076.98/s8.11ms184,671summarysource
fastify-app-v5.6.23,069.37/s8.13ms184,250summarysource
go-app-v1.242,690.25/s9.27ms161,468summarysource
java24-spring-boot-v4.0.12,689.82/s9.27ms161,479summarysource
npgsqlrest-aot-v3.2.22,680.35/s9.31ms160,889summarysource
npgsqlrest-aot-v2.36.22,672.30/s9.34ms160,415summarysource
bun-app-v1.1.422,623.85/s9.51ms157,496summarysource
net10-minapi-dapper-jit2,516.75/s9.92ms151,063summarysource
net9-minapi-ef-jit2,165.76/s11.53ms129,994summarysource
net10-minapi-ef-jit2,115.07/s11.80ms126,958summarysource
django-app-v6.01,631.21/s15.31ms97,916summarysource
fastapi-app-v0.127.11,382.42/s18.07ms82,968summarysource
postgrest-v12.2.81,236.31/s20.21ms74,261summarysource

50 Virtual Users, 10 Records

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
swoole-php-app-v8.4.01,550.33/s16.09ms93,054summarysource
go-app-v1.241,418.95/s17.58ms85,177summarysource
fastify-app-v5.6.21,358.58/s18.37ms81,563summarysource
npgsqlrest-aot-v3.2.21,291.89/s19.31ms77,534summarysource
npgsqlrest-aot-v2.36.21,286.78/s19.38ms77,240summarysource
npgsqlrest-jit-v3.2.21,261.29/s19.77ms75,728summarysource
net10-minapi-dapper-jit1,213.52/s20.56ms72,832summarysource
rust-app-v1.83.01,207.68/s20.65ms72,479summarysource
bun-app-v1.1.421,156.78/s21.58ms69,470summarysource
django-app-v6.01,147.59/s21.77ms68,893summarysource
net10-minapi-ef-jit1,108.81/s22.50ms66,554summarysource
net9-minapi-ef-jit1,096.59/s22.75ms65,825summarysource
java24-spring-boot-v4.0.11,017.51/s24.52ms61,067summarysource
postgrest-v12.2.8967.41/s25.80ms58,074summarysource
fastapi-app-v0.127.1561.29/s44.53ms33,708summarysource

50 Virtual Users, 100 Records

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
swoole-php-app-v8.4.0279.99/s89.21ms16,825summarysource
django-app-v6.0264.75/s94.36ms15,908summarysource
go-app-v1.24240.27/s103.96ms14,440summarysource
rust-app-v1.83.0230.71/s108.31ms13,864summarysource
npgsqlrest-aot-v2.36.2227.73/s109.71ms13,694summarysource
npgsqlrest-aot-v3.2.2227.73/s109.71ms13,689summarysource
fastify-app-v5.6.2215.71/s115.88ms12,965summarysource
bun-app-v1.1.42214.08/s116.71ms12,870summarysource
net10-minapi-dapper-jit209.89/s119.05ms12,623summarysource
postgrest-v12.2.8200.99/s124.31ms12,079summarysource
net10-minapi-ef-jit197.63/s126.39ms11,888summarysource
net9-minapi-ef-jit194.97/s128.13ms11,723summarysource
npgsqlrest-jit-v3.2.2179.29/s139.32ms10,779summarysource
java24-spring-boot-v4.0.1175.88/s142.10ms10,578summarysource
fastapi-app-v0.127.182.45/s303.36ms4,984summarysource

50 Virtual Users, 500 Records

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
django-app-v6.062.00/s403.49ms3,739summarysource
swoole-php-app-v8.4.060.35/s414.86ms3,647summarysource
rust-app-v1.83.053.40/s468.49ms3,226summarysource
go-app-v1.2451.97/s481.89ms3,143summarysource
npgsqlrest-aot-v3.2.250.72/s493.04ms3,065summarysource
npgsqlrest-aot-v2.36.250.52/s495.04ms3,051summarysource
bun-app-v1.1.4249.45/s507.96ms2,997summarysource
net10-minapi-dapper-jit46.38/s538.86ms2,803summarysource
fastify-app-v5.6.246.36/s540.08ms2,804summarysource
postgrest-v12.2.845.12/s554.25ms2,728summarysource
net10-minapi-ef-jit43.98/s571.26ms2,673summarysource
net9-minapi-ef-jit43.33/s576.77ms2,626summarysource
npgsqlrest-jit-v3.2.240.95/s611.07ms2,481summarysource
java24-spring-boot-v4.0.139.34/s635.85ms2,381summarysource
fastapi-app-v0.127.117.99/s1,393.14ms1,121summarysource

100 Virtual Users, 1 Record

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
npgsqlrest-jit-v3.2.25,177.74/s9.64ms310,753summarysource
swoole-php-app-v8.4.03,855.88/s12.95ms231,448summarysource
rust-app-v1.83.03,432.72/s14.54ms206,089summarysource
fastify-app-v5.6.23,072.85/s16.25ms184,513summarysource
go-app-v1.242,691.84/s18.55ms161,662summarysource
java24-spring-boot-v4.0.12,688.93/s18.56ms161,477summarysource
bun-app-v1.1.422,627.67/s19.00ms157,837summarysource
npgsqlrest-aot-v2.36.22,596.05/s19.24ms155,916summarysource
npgsqlrest-aot-v3.2.22,552.41/s19.56ms153,289summarysource
net10-minapi-dapper-jit2,467.96/s20.24ms148,232summarysource
net9-minapi-ef-jit2,180.88/s22.90ms130,981summarysource
net10-minapi-ef-jit2,149.92/s23.23ms129,141summarysource
django-app-v6.01,660.52/s30.10ms99,684summarysource
fastapi-app-v0.127.11,310.52/s38.14ms78,686summarysource
postgrest-v12.2.8843.06/s59.34ms50,779summarysource

100 Virtual Users, 10 Records

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
npgsqlrest-jit-v3.2.22,446.61/s20.41ms146,871summarysource
swoole-php-app-v8.4.01,623.96/s30.69ms97,490summarysource
go-app-v1.241,458.33/s34.21ms87,547summarysource
fastify-app-v5.6.21,383.29/s36.08ms83,064summarysource
npgsqlrest-aot-v3.2.21,321.02/s37.79ms79,362summarysource
npgsqlrest-aot-v2.36.21,313.01/s38.01ms78,842summarysource
bun-app-v1.1.421,271.81/s39.23ms76,375summarysource
net10-minapi-dapper-jit1,263.76/s39.44ms75,905summarysource
rust-app-v1.83.01,254.17/s39.79ms75,305summarysource
django-app-v6.01,190.53/s41.99ms71,502summarysource
net10-minapi-ef-jit1,143.00/s43.66ms68,631summarysource
net9-minapi-ef-jit1,119.29/s44.59ms67,200summarysource
java24-spring-boot-v4.0.11,043.23/s47.80ms62,649summarysource
postgrest-v12.2.8753.64/s66.33ms45,283summarysource
fastapi-app-v0.127.1524.94/s95.13ms31,642summarysource

100 Virtual Users, 100 Records

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
npgsqlrest-jit-v3.2.2406.89/s122.84ms24,464summarysource
swoole-php-app-v8.4.0285.81/s174.80ms17,212summarysource
django-app-v6.0280.10/s178.48ms16,851summarysource
go-app-v1.24246.58/s202.58ms14,839summarysource
rust-app-v1.83.0238.05/s209.84ms14,325summarysource
npgsqlrest-aot-v3.2.2234.23/s213.13ms14,114summarysource
npgsqlrest-aot-v2.36.2232.84/s214.45ms14,020summarysource
bun-app-v1.1.42222.69/s224.31ms13,410summarysource
fastify-app-v5.6.2219.62/s227.42ms13,224summarysource
net10-minapi-dapper-jit216.44/s230.76ms13,036summarysource
postgrest-v12.2.8205.02/s243.72ms12,364summarysource
net10-minapi-ef-jit201.56/s247.84ms12,146summarysource
net9-minapi-ef-jit199.60/s250.27ms12,022summarysource
java24-spring-boot-v4.0.1182.93/s273.12ms11,028summarysource
fastapi-app-v0.127.181.05/s618.46ms4,956summarysource

100 Virtual Users, 500 Records

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
npgsqlrest-jit-v3.2.2100.90/s494.84ms6,098summarysource
django-app-v6.064.22/s778.96ms3,894summarysource
swoole-php-app-v8.4.062.99/s793.21ms3,824summarysource
rust-app-v1.83.054.94/s911.01ms3,336summarysource
go-app-v1.2453.40/s936.01ms3,248summarysource
npgsqlrest-aot-v3.2.251.71/s972.24ms3,150summarysource
npgsqlrest-aot-v2.36.251.66/s976.33ms3,161summarysource
bun-app-v1.1.4250.68/s987.55ms3,077summarysource
net10-minapi-dapper-jit47.32/s1,057.47ms2,882summarysource
fastify-app-v5.6.247.29/s1,056.71ms2,864summarysource
postgrest-v12.2.845.57/s1,098.04ms2,777summarysource
net10-minapi-ef-jit44.43/s1,127.11ms2,704summarysource
net9-minapi-ef-jit44.07/s1,135.02ms2,684summarysource
java24-spring-boot-v4.0.140.43/s1,237.52ms2,469summarysource
fastapi-app-v0.127.117.99/s2,796.24ms1,161summarysource

Released under the MIT License.