DEV Community

Cover image for Performance Comparison of Web Backend and Database: A Case Study of Node.js, Golang, and MySQL, MongoDB
Fu'ad Husnan
Fu'ad Husnan

Posted on

Performance Comparison of Web Backend and Database: A Case Study of Node.js, Golang, and MySQL, MongoDB

Choosing the right combination of web backend and database technology can make or break your application's performance at scale. The performance comparison of Node.js, Golang, MySQL, and MongoDB is a topic that comes up constantly in architecture discussions — and for good reason. Each pairing comes with a distinct set of trade-offs that don't reveal themselves until you're handling real traffic, complex queries, or high write throughput. This article breaks down how these technologies perform against each other and, more importantly, when to use which combination.

Why the Backend-Database Pairing Matters More Than You Think

Most performance discussions treat the backend runtime and the database as independent concerns. In practice, they're deeply intertwined. A blazing-fast backend framework paired with a slow database query layer won't save you. Similarly, a well-indexed relational database connected to a poorly threaded backend will create bottlenecks in places that are hard to diagnose.

The real question isn't "is Node.js faster than Golang?" in isolation — it's how each runtime behaves under load when it's actually waiting on I/O, serializing data, and managing concurrent connections to a database. That's the context where differences become meaningful.

Node.js vs. Golang: A Runtime-Level Overview

Node.js runs on a single-threaded event loop powered by V8. It handles concurrency through non-blocking I/O, which means it can juggle thousands of simultaneous connections without spawning new threads for each. This model works exceptionally well for I/O-bound workloads — exactly what most web APIs are. If your backend is spending most of its time waiting for database responses or external API calls, Node.js handles that waiting efficiently.

Golang takes a fundamentally different approach. It uses goroutines — lightweight, cooperatively scheduled units of work managed by Go's runtime — that can run in true parallelism across multiple CPU cores. Goroutines are cheaper than OS threads (a fresh goroutine uses about 2KB of stack space versus megabytes for a thread), so you can spawn hundreds of thousands of them without crashing the system. This makes Go particularly strong for CPU-intensive tasks and for scenarios where you need both high concurrency and heavy computation happening simultaneously.

For pure throughput benchmarks on simple REST endpoints, Golang consistently edges out Node.js — often by 30–50% in requests per second under heavy load. But for typical CRUD-heavy APIs, the gap narrows considerably because both runtimes spend most of their time waiting on the database anyway.

Setting Up a Baseline: Simple HTTP Handlers

To make this concrete, here's what a minimal HTTP endpoint looks like in each language. Both respond to a GET request and return a JSON payload.

Node.js with Express:

``JavaScriptt
const express = require('express');
const app = express();

app.get('/ping', (req, res) => {
res.json({ message: 'pong', timestamp: Date.now() });
});

app.listen(3000, () => console.log('Server running on port 3000'));
`go

Golang with the standard net/http package:

`go
package main

import (
"encoding/json"
"net/http"
"time"
)

func pingHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"message": "pong",
"timestamp": time.Now().UnixMilli(),
})
}

func main() {
http.HandleFunc("/ping", pingHandler)
http.ListenAndServe(":3000", nil)
}
`

On a benchmark tool like wrk or hey, the Go version will typically handle more requests per second at lower latency — but for a ping endpoint that does no I/O, the difference is largely academic. The story gets more interesting when you plug in a real database.

MySQL vs. MongoDB: The Database Half of the Equation

MySQL is a mature relational database with decades of optimization behind it. It excels at complex joins, transactional integrity, and structured queries where you know your schema ahead of time. The query planner is sophisticated, and with proper indexing, MySQL can handle millions of rows without breaking a sweat.

MongoDB is a document-oriented database that stores data as BSON (Binary JSON). Its schema-free model is genuinely useful when your data structure is evolving rapidly or when your documents naturally have nested, variable-length fields. MongoDB also has a strong story around horizontal sharding — spreading data across multiple nodes is a first-class feature, whereas MySQL sharding is operationally more complex.

The performance comparison between MySQL and MongoDB isn't purely about speed. It's about the type of operations you're running. For write-heavy workloads with simple documents, MongoDB can outperform MySQL because it doesn't enforce strict ACID compliance by default (though it does support it now). For read-heavy workloads with complex relational queries, MySQL with good indexes will typically win.

Node.js + MySQL: The Classic Enterprise Stack

Connecting Node.js to MySQL is well-understood territory. The mysql2 package with connection pooling is the go-to setup, and it handles most production workloads comfortably.

JavaScriptt
const mysql = require('mysql2/promise');

const pool = mysql.createPool({
host: 'localhost',
user: 'root',
database: 'app_db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});

async function getUserById(id) {
const [rows] = await pool.execute(
'SELECT id, name, email FROM users WHERE id = ?',
[id]
);
return rows[0];
}
`javascript

Connection pooling is critical here. Without it, Node.js would open a new TCP connection for every request — a cost that accumulates rapidly under load. With a pool of 10 connections and an async/await pattern, this setup can handle several hundred concurrent requests without significant degradation. The weakness shows up when you need complex aggregations or deeply nested joins, where MySQL's blocking query execution can stack up in the event loop queue.

Node.js + MongoDB: Fast Writes, Flexible Schema

For applications where the data model changes often — early-stage products, content management systems, user-generated content — the Node.js and MongoDB combination feels natural. Mongoose is the dominant ODM, though the native MongoDB driver gives you more control and better raw performance.

`JavaScript
const { MongoClient } = require('mongodb');

const client = new MongoClient('mongodb://localhost:27017');
const db = client.db('app_db');

async function createPost(postData) {
const result = await db.collection('posts').insertOne({
...postData,
createdAt: new Date(),
});
return result.insertedId;
}
`

MongoDB's write throughput in this setup is noticeably higher than MySQL for bulk inserts of unstructured documents, largely because it doesn't validate a schema or update secondary indexes synchronously by default. Under benchmarks with 10,000 concurrent inserts, MongoDB often completes 20–30% faster than MySQL for document-style records.

Golang + MySQL: High Throughput with Structure

Go's database/sql package, combined with a driver like go-sql-driver/mysql, gives you a strongly typed, pool-managed database layer with minimal overhead. The combination shines for backend services that need to handle high request rates against a well-structured data model.

`go
package main

import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"log"
)

var db *sql. DB

func init() {
var err error
db, err = sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/app_db")
if err != nil {
log.Fatal(err)
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
}

func getUserByID(id int) (string, string, error) {
var name, email string
err := db.QueryRow("SELECT name, email FROM users WHERE id = ?", id).Scan(&name, &email)
return name, email, err
}
`

The key difference from Node.js here is that Go handles each request on its own goroutine. When QueryRow blocks waiting for the MySQL response, only that goroutine is paused — the rest of the server continues processing. This is fundamentally more efficient under sustained load than Node.js's event loop model for CPU-parallel workloads.

Golang + MongoDB: Speed and Flexibility at the Cost of Simplicity

The Go MongoDB driver is officially maintained by MongoDB, and it's performant. The trade-off is verbosity — Go's static typing means you either define structs for your documents or work with bson.M maps, which feel clunky compared to JavaScript's dynamic nature.

`go
package main

import (
"context"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"time"
)

func insertDocument(client *mongo.Client, data bson.M) error {
coll := client.Database("app_db").Collection("posts")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

_, err := coll.InsertOne(ctx, data)
return err
Enter fullscreen mode Exit fullscreen mode

}
`

In raw throughput tests, Go + MongoDB is the fastest combination for insert-heavy workloads. The goroutine model allows the backend to keep thousands of simultaneous write operations in flight without the serialization bottleneck you'd see in Node.js's single-threaded model.

What the Numbers Actually Tell You

When running a realistic benchmark — say, 500 concurrent users hitting an endpoint that reads a record from the database — the results roughly follow this pattern. Go + MySQL and Go + MongoDB cluster near the top for requests per second at low latency. Node.js + MongoDB tends to perform well for mixed read-write workloads due to MongoDB's write speed. Node.js + MySQL lands in a comfortable middle ground that handles most business applications without drama.

The more important insight is that after a certain point, database indexing and query optimization matter more than runtime choice. A missing index in MySQL will tank any backend's performance, regardless of whether it's written in Go or JavaScript. Profile your database first.

Conclusion

There's no universally "best" stack — the right combination depends on your workload profile, team expertise, and long-term scalability needs. If you're building a high-throughput service where CPU performance matters and your schema is stable, Go + MySQL is a hard combination to beat. If you're moving fast on a product with evolving data structures and a JavaScript-heavy team, Node.js + MongoDB lets you iterate quickly without sacrificing too much performance. The practical advice: run your own benchmarks under your actual load patterns, index your databases properly, and don't over-engineer before you have real traffic to profile against.

Start with what your team knows well, measure under realistic conditions, and optimize from there — that's where the real performance gains live.

Top comments (0)