Choorai
Lv.2

Relational Database (RDB)

The most widely used type of database. Define relationships between tables and query data using SQL.

What is this?

The most common type of DB that stores data in tables and rows. Queried using SQL.

1When do I need this?

  • Structured data like users/orders/products
  • Relationships between data matter (users - orders - products)
  • Complex queries are needed (JOIN, GROUP BY, aggregation)
  • Transaction guarantees are required (payments, inventory management)

2Key Services

PostgreSQL

Free, most popular

Open source. Rich in JSON, full-text search, and extension features. Recommended for most new projects.

MySQL

Free, many legacy systems

Long history and massive ecosystem. Widely used in WordPress and existing systems.

Supabase

Managed PostgreSQL

PostgreSQL-based BaaS. Provides authentication, storage, and real-time features together.

PlanetScale

Managed MySQL

MySQL compatible. Safely manage schema changes with branching.

3Pricing

  • PostgreSQL / MySQL - Free when self-hosted (server costs separate)
  • Supabase - Free tier: 500MB storage, 50K API requests/month
  • PlanetScale - Free tier: 5GB storage, 1B row reads/month

4Connection Examples

FastAPI + asyncpg (Python)

app/database.py
# app/database.py
import asyncpg
from contextlib import asynccontextmanager

DATABASE_URL = "postgresql://user:password@localhost:5432/mydb"

pool = None

async def init_db():
    global pool
    pool = await asyncpg.create_pool(DATABASE_URL)

async def close_db():
    await pool.close()

async def get_connection():
    async with pool.acquire() as conn:
        yield conn
app/main.py
# app/main.py
from fastapi import FastAPI, Depends
from app.database import init_db, close_db, get_connection

app = FastAPI()

@app.on_event("startup")
async def startup():
    await init_db()

@app.on_event("shutdown")
async def shutdown():
    await close_db()

@app.get("/users")
async def get_users():
    async with pool.acquire() as conn:
        rows = await conn.fetch("SELECT id, name, email FROM users")
        return [dict(r) for r in rows]

@app.post("/users")
async def create_user(name: str, email: str):
    async with pool.acquire() as conn:
        row = await conn.fetchrow(
            "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *",
            name, email
        )
        return dict(row)

Hono + postgres.js (TypeScript)

src/db.ts
// src/db.ts
import postgres from 'postgres';

const sql = postgres('postgresql://user:password@localhost:5432/mydb');

export default sql;
src/index.ts
// src/index.ts
import { Hono } from 'hono';
import sql from './db';

const app = new Hono();

app.get('/users', async (c) => {
  const users = await sql`SELECT id, name, email FROM users`;
  return c.json(users);
});

app.post('/users', async (c) => {
  const { name, email } = await c.req.json();
  const [user] = await sql`
    INSERT INTO users (name, email)
    VALUES (${name}, ${email})
    RETURNING *
  `;
  return c.json(user, 201);
});

export default app;

Using an ORM is more convenient

In real projects, it's more common to use SQLAlchemy (Python) or Drizzle ORM (TypeScript) rather than writing raw SQL.

Last updated: February 22, 2026 · Version: v0.0.1

Send Feedback

Opens a new issue page with your message.

Open GitHub Issue