← Backend← Backend
BackendBackend19 Th7, 2026Jul 19, 202619 phút đọc15 min read

Ngôn ngữ lập trình BackendBackend Programming Languages

Thuộc bộ kiến thức Backend Roadmap.

Tổng quan

Không có một ngôn ngữ lập trình backend nào là “tốt nhất” tuyệt đối. Mọi ngôn ngữ phổ biến đều có thể xây dựng một web service đúng đắn và đủ nhanh — điểm khác biệt thực sự nằm ở ecosystem, mô hình concurrency/runtime, hồ sơ vận hành (memory, thời gian khởi động, cách deploy), và con người viết cũng như bảo trì code đó.

Note này giúp bạn suy nghĩ về cách chọn ngôn ngữ thay vì học thuộc một bảng xếp hạng. Nó khảo sát các ngôn ngữ backend chính, giải thích các mô hình runtime tạo ra khác biệt giữa chúng, cung cấp một bảng so sánh lớn, và đưa ra các đoạn code “hello HTTP server” tối giản để bạn cảm nhận sự tiện dụng của từng ngôn ngữ.

Nếu bạn mới bắt đầu, hãy đọc Introduction to Backend trước để có cái nhìn tổng thể, và APIs để hiểu cách các ngôn ngữ này thực sự cung cấp chức năng qua network.

Cách tư duy khi “chọn ngôn ngữ”

Chọn ngôn ngữ backend là một sự đánh đổi (trade-off) trên nhiều tiêu chí. Hãy xếp thứ tự ưu tiên chúng cho tình huống của bạn trước khi tranh luận về syntax:

Một lựa chọn mặc định hữu ích: chọn ngôn ngữ mà team làm việc hiệu quả nhất, trừ khi một yêu cầu cụ thể (throughput, latency, memory, một thư viện trọng yếu) buộc bạn phải khác đi.

Kiến thức nền tảng

Mô hình runtime và concurrency

Khác biệt khái niệm lớn nhất giữa các ngôn ngữ backend là cách chúng xử lý concurrency — phục vụ nhiều client cùng lúc mà không phải khóa (block) một OS thread riêng cho mỗi request. Có bốn mô hình chủ đạo.

1. Thread-per-request (blocking I/O)

Mỗi request đến được cấp một OS thread (hoặc lấy từ thread pool). Thread bị block trong lúc chờ database, disk, hay network.

2. Event loop (single-threaded, non-blocking I/O)

Một thread chạy một event loop. Các thao tác I/O là non-blocking; khi I/O xong, một callback/promise được lên lịch. Công việc CPU-bound phải được đẩy ra ngoài (worker threads, process riêng) nếu không sẽ làm nghẽn tất cả.

3. Goroutines / green threads (M:N scheduling)

Runtime ghép (multiplex) nhiều “thread” nhẹ ở user-space lên một số ít OS thread. Block một thread nhẹ không block OS thread — scheduler đỗ (park) nó lại và chạy cái khác.

4. async/await (hợp tác, tường minh)

Các hàm được đánh dấu async; await một future sẽ nhường quyền điều khiển lại cho scheduler/executor. Lợi ích tương tự event loop nhưng với syntax tường minh, và thường có executor đa luồng bên dưới.

Quy tắc kinh nghiệm: phần lớn web backend là I/O-bound (chờ DB/network), nên các mô hình event-loop, goroutine, và async đều tỏa sáng. Hãy dùng thread với parallelism thật hoặc một systems language (Go/Rust/C++) khi công việc thực sự CPU-bound.

Typing: static và dynamic

TypeScript và Python type hints là điểm dung hòa phổ biến: gradual typing được phủ lên một ngôn ngữ dynamic.

Compiled và interpreted / JIT

Khái niệm chính

Khảo sát các ngôn ngữ

Dưới đây là từng ngôn ngữ backend chính cùng thị trường ngách (niche), các framework phổ biến, và những đánh đổi thẳng thắn.

JavaScript / TypeScript (Node.js)

Python

Go

Java (JVM) và Kotlin

C# / .NET

Ruby

PHP

Rust

C / C++ (ngắn gọn)

Bảng so sánh

Ngôn ngữTypingMô hình concurrencyFrameworks tiêu biểuĐiểm mạnhUse case phổ biến
JavaScript / TypeScript (Node.js)Dynamic (TS: static)Event loop đơn luồng, async/awaitExpress, Fastify, NestJS, HonoFull-stack JS, ecosystem lớn, hợp I/O & real-time, edgeAPI REST/GraphQL, BFF, real-time, serverless
PythonDynamic (type hints)Thread-per-request (WSGI) hoặc asyncio (ASGI); GILDjango, FastAPI, FlaskDễ đọc, năng suất, thống trị ML/dataPhát triển nhanh, backend gần ML, app admin, API async
GoStaticGoroutines + channels (M:N)net/http, Gin, Echo, Fiber, ChiConcurrency cao mà đơn giản, khởi động nhanh, binary nhỏMicroservices, API, gateway, công cụ DevOps/CLI
Java (JVM)StaticThreads / virtual threads (Loom); reactiveSpring Boot, Quarkus, MicronautTrưởng thành doanh nghiệp, ecosystem, steady-state perfHệ thống doanh nghiệp lớn, dịch vụ throughput cao
Kotlin (JVM)StaticCoroutines; JVM threadsKtor, Spring BootSúc tích, null-safe, coroutines, interop JVMBackend JVM hiện đại, team gần Android
C# / .NETStaticasync/await (Task), threads thậtASP.NET Core, Minimal APIsNhanh, tooling trau chuốt, cross-platformApp doanh nghiệp, tổ chức Azure/Windows, API hiệu năng cao
RubyDynamicThread-per-request (GVL); fibers/RactorsRuby on RailsHạnh phúc lập trình viên, CRUD nhanhStartup, SaaS, web app cổ điển
PHPDynamic (có tính năng typed)Worker per-request; coroutine Swoole/RoadRunnerLaravel, SymfonyPhổ biến, deploy dễ, PHP 8 nhanh, DX tốtWeb app, CMS, SaaS, MVP nhanh
RustStaticasync/await trên tokio; không GCAxum, Actix Web, RocketTốc độ native + an toàn bộ nhớ, footprint nhỏDịch vụ nhạy latency, systems, WASM
C / C++StaticThreads, thủ côngDrogon, Crow, PistachePerformance/kiểm soát tối đaSystems, embedded, ngách low-latency

Đoạn code “hello HTTP server” tối giản

Cùng một ý tưởng trong ba ecosystem — khởi động server, phản hồi một request. Hãy để ý cách mỗi đoạn phản ánh mô hình concurrency của nó.

Go (thư viện chuẩn net/http): mỗi request được phục vụ trên goroutine riêng một cách tự động.

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, HTTP from Go!")
	})
	// Mỗi request đến chạy trong goroutine riêng của nó.
	http.ListenAndServe(":8080", nil)
}

Node.js (JavaScript, module http tích hợp sẵn): một event loop duy nhất xử lý mọi connection.

const http = require("http");

const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Hello, HTTP from Node.js!\n");
});

// Một event loop, non-blocking I/O.
server.listen(8080, () => console.log("Listening on :8080"));

Cũng như vậy với Express (framework Node phổ biến nhất):

const express = require("express");
const app = express();

app.get("/", (req, res) => res.send("Hello, HTTP from Express!"));

app.listen(8080, () => console.log("Listening on :8080"));

Python (FastAPI, async): định nghĩa một endpoint async; chạy bằng ASGI server (uvicorn).

# pip install fastapi uvicorn
# chạy: uvicorn main:app --reload --port 8080
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello, HTTP from FastAPI!"}

Best Practices

Tài liệu tham khảo

Part of the Backend Roadmap knowledge base.

Overview

There is no single “best” backend programming language. Every mainstream language can build a correct, fast-enough web service — the differences that matter in practice are the ecosystem, the concurrency/runtime model, the operational profile (memory, startup time, deployment), and the people who will write and maintain the code.

This note helps you reason about how to choose a language rather than memorize a ranking. It surveys the major backend languages, explains the runtime models that separate them, provides a large comparison table, and shows minimal “hello HTTP server” snippets so you can feel the ergonomics of each.

If you are new here, read Introduction to Backend first for the big picture, and APIs for how these languages actually expose functionality over the network.

How to think about “picking a language”

Choosing a backend language is a trade-off across several axes. Rank them for your situation before you argue about syntax:

A useful default: pick the language your team is most productive in unless a specific requirement (throughput, latency, memory, a critical library) forces otherwise.

Fundamentals

Runtime and concurrency models

The single biggest conceptual difference between backend languages is how they handle concurrency — serving many clients at once without a dedicated OS thread blocked per request. There are four dominant models.

1. Thread-per-request (blocking I/O)

Each incoming request gets an OS thread (or a thread from a pool). The thread blocks while waiting on the database, disk, or network.

2. Event loop (single-threaded, non-blocking I/O)

One thread runs an event loop. I/O operations are non-blocking; when I/O completes, a callback/promise is scheduled. CPU-bound work must be offloaded (worker threads, separate processes) or it stalls everything.

3. Goroutines / green threads (M:N scheduling)

The runtime multiplexes many lightweight user-space “threads” onto a small number of OS threads. Blocking a lightweight thread does not block an OS thread — the scheduler parks it and runs another.

4. async/await (cooperative, explicit)

Functions are marked async; awaiting a future yields control back to a scheduler/executor. Similar benefits to the event loop but with explicit syntax, and often multi-threaded executors underneath.

Rule of thumb: most web backends are I/O-bound (waiting on DB/network), so event-loop, goroutine, and async models all shine. Reach for threads-with-real-parallelism or a systems language (Go/Rust/C++) when work is genuinely CPU-bound.

Typing: static vs dynamic

TypeScript and Python type hints represent a popular middle ground: gradual typing layered onto a dynamic language.

Compiled vs interpreted / JIT

Key Concepts

Language survey

Below, each major backend language with its niche, common frameworks, and honest trade-offs.

JavaScript / TypeScript (Node.js)

Python

Go

Java (JVM) and Kotlin

C# / .NET

Ruby

PHP

Rust

C / C++ (brief)

Comparison table

LanguageTypingConcurrency modelTypical frameworksStrengthsCommon use cases
JavaScript / TypeScript (Node.js)Dynamic (TS: static)Single-thread event loop, async/awaitExpress, Fastify, NestJS, HonoFull-stack JS, huge ecosystem, great for I/O & real-time, edgeREST/GraphQL APIs, BFFs, real-time, serverless
PythonDynamic (type hints)Thread-per-request (WSGI) or asyncio (ASGI); GILDjango, FastAPI, FlaskReadability, velocity, ML/data dominanceRapid dev, ML-adjacent backends, admin apps, async APIs
GoStaticGoroutines + channels (M:N)net/http, Gin, Echo, Fiber, ChiSimple high concurrency, fast startup, small binariesMicroservices, APIs, gateways, DevOps/CLI tooling
Java (JVM)StaticThreads / virtual threads (Loom); reactiveSpring Boot, Quarkus, MicronautEnterprise maturity, ecosystem, steady-state perfLarge enterprise systems, high-throughput services
Kotlin (JVM)StaticCoroutines; JVM threadsKtor, Spring BootConcise, null-safe, coroutines, JVM interopModern JVM backends, Android-adjacent teams
C# / .NETStaticasync/await (Task), real threadsASP.NET Core, Minimal APIsFast, polished tooling, cross-platformEnterprise apps, Azure/Windows shops, high-perf APIs
RubyDynamicThread-per-request (GVL); fibers/RactorsRuby on RailsDeveloper happiness, rapid CRUDStartups, SaaS, classic web apps
PHPDynamic (typed features)Per-request worker; Swoole/RoadRunner coroutinesLaravel, SymfonyUbiquitous, easy deploy, PHP 8 perf, great DXWeb apps, CMS, SaaS, fast MVP
RustStaticasync/await on tokio; no GCAxum, Actix Web, RocketNative speed + memory safety, tiny footprintLatency-critical services, systems, WASM
C / C++StaticThreads, manualDrogon, Crow, PistacheMaximum performance/controlSystems, embedded, low-latency niches

Minimal “hello HTTP server” snippets

The same idea in three ecosystems — start a server, respond to a request. Notice how each reflects its concurrency model.

Go (standard library net/http): each request is served on its own goroutine automatically.

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, HTTP from Go!")
	})
	// Each incoming request runs in its own goroutine.
	http.ListenAndServe(":8080", nil)
}

Node.js (JavaScript, built-in http): a single event loop handles all connections.

const http = require("http");

const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Hello, HTTP from Node.js!\n");
});

// One event loop, non-blocking I/O.
server.listen(8080, () => console.log("Listening on :8080"));

The same in Express (the most common Node framework):

const express = require("express");
const app = express();

app.get("/", (req, res) => res.send("Hello, HTTP from Express!"));

app.listen(8080, () => console.log("Listening on :8080"));

Python (FastAPI, async): define an async endpoint; run with an ASGI server (uvicorn).

# pip install fastapi uvicorn
# run: uvicorn main:app --reload --port 8080
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello, HTTP from FastAPI!"}

Best Practices

References