8  0. Fundamentos: lo que ya casi sabes

El piso mínimo para leer todo este libro — y salir sabiendo backend

NotaEn una frase

Repaso exprés de lo que ya casi sabes: terminal, HTTP, JSON, clases y cómo se define un endpoint — traducido a TypeScript, Python y Go. Lee las secciones que necesites; el checklist del final te dice si estás listo.

Este capítulo es el único “teórico” del libro. Su trabajo es simple: que nada de lo que viene después te pille desprevenido. Si vienes de JavaScript, ya sabes el 80% — aquí solo traducimos lo que sabes y rellenamos los huecos que un frontend nunca necesitó.

No memorices esto. Es material de consulta: puedes saltarlo, seguir el libro, y volver aquí cuando algo no te suene. El libro está pensado para leerse con un AI CLI al lado — pregúntale “explícame X de este capítulo” sin culpa.

Al final hay un checklist: si puedes marcarlo todo, puedes leer el libro entero sin frenarte.

8.1 0.1 La terminal: tu otro navegador

En backend, la terminal es donde vive tu aplicación — no hay DevTools que te salve. Cinco cosas que usarás todos los días:

La terminal (consola, línea de comandos) es la interfaz de texto con el sistema operativo: escribes comandos, lees resultados. Es como hablarle a la computadora por cartas en vez de señalar con el mouse.

cd mi-proyecto          # entrar a una carpeta
ls                      # ver qué hay (en Windows: dir)
curl http://localhost:8000/health      # llamar una API sin frontend
curl -X POST http://localhost:8000/tasks \
  -H "Content-Type: application/json" \
  -d '{"title": "learn backend"}'      # POST con JSON body
export DEBUG=true       # variable de entorno (Windows PowerShell: $env:DEBUG="true")

curl es tu fetch de terminal: verás en el libro que casi todo endpoint se prueba primero con curl — si funciona ahí, funcionará en tu React.

8.2 0.2 HTTP en quince minutos

Ya usas HTTP desde fetch, pero ahora tú eres quien responde. Un request tiene cuatro piezas:

POST /tasks?urgent=true HTTP/1.1     ← método + ruta + query
Host: api.ejemplo.com                ┐
Content-Type: application/json       ├─ headers (metadatos)
Authorization: Bearer eyJhbGc…       ┘

{"title": "deploy", "priority": 2}   ← body (opcional)

Y una response, tres:

HTTP/1.1 201 Created                 ← status code
Content-Type: application/json       ← headers

{"id": 7, "title": "deploy", "done": false}   ← body

Los métodos son semántica, no decoración:

Método Promesa Ejemplo
GET Solo leo, nunca modifico GET /tasks
POST Creo o ejecuto algo POST /tasks
PUT Reemplazo el recurso entero PUT /tasks/7
PATCH Modifico parte PATCH /tasks/7
DELETE Elimino DELETE /tasks/7

Los status codes son familias: 2xx salió bien, 4xx culpa del cliente (mal body, sin permiso, no existe), 5xx culpa del servidor (tu bug). Un backend profesional elige el código a propósito — es parte del contrato, no un detalle.

La autorización responde “¿qué puedes hacer?”: eres usuario válido (autenticado), pero ¿puedes borrar esta tarea? Se decide por rol o por ownership — y se verifica en cada request, no se recuerda.

8.3 0.3 JSON: el idioma común

JSON es un formato de texto para datos — hijo de JavaScript, adoptado por todos los lenguajes:

{
  "id": 7,
  "title": "deploy",
  "done": false,
  "tags": ["ops", "release"],
  "owner": { "id": 3, "name": "Ana" },
  "due": null
}

Tipos: string, number, boolean, null, array [], objeto {}. No hay funciones, ni undefined, ni fechas — una fecha viaja como string ISO "2026-10-01" y cada lado la interpreta.

Una función es una receta reutilizable: recibe ingredientes (parámetros), hace pasos y devuelve un plato (return). La escribes una vez y la llamas mil veces: add(2, 3) → 5.

Un array es una colección ordenada de elementos accedidos por posición: ["a","b","c"][0] es "a" (se cuenta desde 0). Python las llama listas, Go slices — misma idea, distinto acento.

Un objeto es una colección de datos con nombre: {name: "Ana", age: 30} — cada dato es una propiedad (clave → valor). Python los llama dict, Go los arma con struct, TS con objetos/interface.

Un string es texto entre comillas: "hola". El nombre viene de “cadena de caracteres” — una secuencia de letras. Todo lo que llega de un formulario o una URL llega como string, aunque parezca número.

Un booleano es un valor de dos estados: true o false. Es el resultado de toda comparación (age > 18) y lo que los if evalúan. Nombrado por George Boole, el matemático de la lógica.

null (TS), None (Py), nil (Go) = “aquí no hay valor”. Es la respuesta a “¿qué devuelvo cuando no hay nada?” — y la fuente del bug más famoso de la historia (su inventor lo llamó “el error del billón de dólares”). Por eso el código revisa if x is not None.

En backend, serializar es convertir tu objeto interno a JSON para la respuesta; parsear es convertir el JSON del request a tu objeto. Harás ambas cosas cientos de veces en este libro.

8.4 0.4 Recordatorio: así llamas a una API desde el frontend

Si esto no te suena del todo: la Sección I · Aprende Frontend lo enseña completo (fetch, axios, React Query, HttpClient). Lee el resumen y vuelve aquí — o salta allá primero si lo prefieres.

El punto que importa para backend: del otro lado de cada una de estas llamadas hay un servidor — y a partir del Cap. 1 ese servidor eres tú.

// fetch — la base de todo
const res = await fetch("/api/tasks");
if (!res.ok) throw new Error(`HTTP ${res.status}`);  // 404 no lanza solo
const tasks = await res.json();

// axios — JSON auto, errores en 4xx, interceptors para auth
const { data } = await axios.get("/api/tasks");

// React Query — loading/error/caché resueltos
const { data, isLoading } = useQuery({
  queryKey: ["tasks"],
  queryFn: () => fetch("/api/tasks").then((r) => r.json()),
});

// Angular HttpClient — Observable (frío): sin subscribe no hay request
this.http.get<Task[]>("/api/tasks").subscribe((t) => (this.tasks = t));

El puente: tu lib/api-client.ts del frontend era el consumidor del contrato — ahora tú produces ese contrato. Y tu services/ del frontend (hablador de APIs) es el espejo del services/ del backend (lógica de negocio) del Cap. 7: mismo nombre, distinto rol.

8.5 0.5 Clases y objetos: por qué importan aquí

Si usaste clases en JS/TS, esto es repaso con traducción. Si no, la idea es simple: una clase es un molde que agrupa datos (atributos) y

Una clase es el molde de un objeto: define qué datos y qué métodos tiene. class Task es el molde; new Task() es una instancia concreta. Go no tiene clases — usa struct + métodos sueltos.

comportamiento (métodos); un objeto es una instancia creada desde ese molde.

¿Por qué le importa esto a un backend? Porque tres piezas centrales se expresan como clases:

  • Schemas/modelos: class TaskCreate define la forma válida del body
  • Modelos de datos: class Task mapea a una fila de tabla
  • Servicios/handlers: agrupan la lógica por dominio
// interface = solo la forma (desaparece al compilar)
interface Task {
  id: number;
  title: string;
  done: boolean;
}

// class = forma + comportamiento (existe en runtime)
class TaskService {
  private tasks: Task[] = [];            // atributo privado

  create(title: string): Task {          // método
    const task = { id: this.tasks.length + 1, title, done: false };
    this.tasks.push(task);               // this = esta instancia
    return task;
  }
}

const svc = new TaskService();           // instancia
svc.create("deploy");
# Python no tiene interfaces — las clases hacen ambos papeles
class Task:
    def __init__(self, id: int, title: str):
        self.id = id          # self = esta instancia (explícito, se escribe)
        self.title = title
        self.done = False

class TaskService:
    def __init__(self):
        self.tasks: list[Task] = []

    def create(self, title: str) -> Task:   # self es el primer parámetro
        task = Task(len(self.tasks) + 1, title)
        self.tasks.append(task)
        return task

svc = TaskService()             # instancia — sin "new"
svc.create("deploy")
// Go no tiene clases: tiene structs (datos) + métodos (funciones pegadas)
type Task struct {
    ID    int
    Title string
    Done  bool
}

type TaskService struct {
    tasks []Task              // campo privado (minúscula = privado en Go)
}

// método: la "instancia" es el receiver (s *TaskService)
func (s *TaskService) Create(title string) Task {
    t := Task{ID: len(s.tasks) + 1, Title: title}
    s.tasks = append(s.tasks, t)
    return t
}

svc := &TaskService{}         // instancia
svc.Create("deploy")

Las diferencias que importan de verdad:

  • self/this/receiver: la referencia a “esta instancia”. En JS se escribe this (y a veces desaparece mágicamente); en Python se declara self como primer parámetro de cada método; en Go es el receiver (s *TaskService) — el método es una función que recibe el struct.

El parámetro es el hueco declarado en la función (def f(x) — x es parámetro); el argumento es el valor concreto que le pasas (f(42) — 42 es argumento). Mismo dato, dos momentos: declaración vs uso.

  • Go no tiene clases ni herencia: struct = datos, método = función con receiver. Suena limitado y a propósito lo es — la composición explícita reemplaza la herencia (lo verás en servicios).
  • En Python todo es objeto y self es obligatorio: def create(self, title) — olvidarlo es el error clásico del que viene de JS.

Esta decisión es material: necesitas alguna forma de darle nombre y estructura a tus datos — pero “clase” es solo una de las cimentaciones.

Alternativa Qué es Qué te cuesta Cuándo elegirla
Clases completas Datos + comportamiento juntos Más ceremonial; tentación de meter lógica de más Servicios con estado (TaskService), patrones OO
Solo forma (interface/DTO) interface TS, TypedDict, struct Sin métodos — solo describe la forma Schemas de entrada/salida (lo más común en APIs)
dataclass / BaseModel Clase reducida a “datos con validación” Dependencia (Pydantic) o menos flexibilidad Python moderno — es lo que FastAPI usa
Structs + funciones Datos y lógica separados (Go) No hay herencia — composición explícita Go: es la forma, no una opción
Funciones + objetos literales Sin tipos nombrados Nada te garantiza la forma Scripts chicos — en backend se queda corto rápido

La regla práctica del libro: los datos que cruzan la frontera (request, response, DB) llevan forma declarada; los servicios que orquestan llevan comportamiento.

8.6 0.6 Funciones, parámetros y módulos

La traducción rápida:

// función tipada
function createTask(title: string, priority: number = 3): Task {
  return { id: 1, title, done: false };
}

import { createTask } from "./services/tasks.js";  // ES module
# def + indentación — los bloques son por sangría, no por llaves
def create_task(title: str, priority: int = 3) -> Task:
    return Task(id=1, title=title)

from app.services.tasks import create_task   # módulo = archivo .py
// func + llaves; el tipo va DESPUÉS del nombre
func createTask(title string, priority int) Task {
    return Task{ID: 1, Title: title}
}

import "taskflow/internal/service"   // package = directorio

Dos detalles Python que confunden al principio:

  • snake_case: create_task, no createTask. Convención del lenguaje.
  • La indentación ES la sintaxis: def/if/for terminan en : y el cuerpo va indentado — no hay {} ni end.

8.7 0.7 Estructuras de datos mínimas

Concepto TypeScript Python Go
Mapa clave→valor object / Map dict map[K]V
Lista ordenada Array list []T (slice)
Conjunto sin duplicados Set set map[K]struct{}
Tupla inmutable readonly [A,B] tuple — (usa struct)
# Python — las que más verás
user = {"id": 3, "name": "Ana"}        # dict ≈ objeto JS
user["name"]                            # acceso por clave
user.get("email", "sin email")          # con default — evita KeyError

tasks = [t1, t2, t3]                    # list ≈ array
active = [t for t in tasks if not t.done]  # comprehension — el .filter() de Python
titles = [t.title for t in tasks]          # el .map() equivalente

Las comprehensions de Python ([x for x in y if cond]) merecen un momento: son .filter() y .map() fusionados en sintaxis — las verás en servicios y queries por todos lados.

Un bucle repite una acción por cada elemento o hasta cumplir una condición: for task in tasks hace algo con cada tarea. map y filter son bucles disfrazados de funciones — transforman/filtran colecciones sin for explícito.

Una query es la pregunta que le haces a la base de datos en SQL: SELECT * FROM tasks WHERE done = false. La BD traduce la pregunta a un plan de búsqueda — por eso los índices importan.

8.8 0.8 Errores en una frase (el Cap. 6 los abre de verdad)

  • TS/Python: throw/raise una excepción; alguien arriba hace catch/except. Si nadie la atrapa → 500.
  • Go: la función devuelve el error como valor: t, err := getTask(id) — no salta nada, tú decides.

8.9 0.9 Qué es un endpoint (la anatomía mínima)

Un endpoint = (verbo + ruta) → handler. El handler es solo una función que recibe el request y produce la response:

// verbo.get(ruta, handler)
app.get("/tasks/:id", (req, res) => {
  const task = findTask(Number(req.params.id));
  if (!task) return res.status(404).json({ error: "not found" });
  res.json(task);                    // 200 + body
});
# decorator: "cuando llegue GET /tasks/{id}, ejecuta esta función"
@app.get("/tasks/{task_id}")
def get_task(task_id: int):
    task = find_task(task_id)
    if not task:
        raise HTTPException(404, "not found")
    return task                      # se serializa a JSON solo
// patrón "método espacio ruta" → handler(w, r)
mux.HandleFunc("GET /tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
    id, _ := strconv.Atoi(r.PathValue("id"))
    if t, ok := findTask(id); ok {
        json.NewEncoder(w).Encode(t)
    } else {
        http.Error(w, "not found", 404)
    }
})

Leído en voz alta, los tres dicen lo mismo: “cuando llegue un GET a /tasks/{id}, corre esta función que busca la tarea y la devuelve como JSON — o responde 404”. El resto del libro solo agrega capas encima de esa idea.

8.10 Ejercicio

  1. Escribe en tu lenguaje elegido una clase/struct User con id, email, is_active y un método deactivate() que la apague.
  2. Haz curl -i http://localhost:8000/tasks contra el servidor del Cap. 3 y lee los headers de la response — encuentra el status y el Content-Type.
  3. Traduce a tu lenguaje: [u.name for u in users if u.is_active] (la comprehension de arriba) a JS: ¿qué combinación de métodos es?

localhost significa “esta misma máquina” — es la dirección que tu computadora usa para hablarse a sí misma. Cuando desarrollas, el “servidor” y el “cliente” viven en tu laptop: por eso todo es localhost:3000.

8.11 Mini reto

Sin código: escribe con tus palabras la diferencia entre la clase Task y un objeto task, y da un ejemplo de cuándo en backend usas cada una. Si puedes explicar por qué un schema de validación (Cap. 4) se define como clase pero un endpoint es una función, entendiste la sección 0.5.

El esquema es el plano de la base de datos: qué tablas hay, qué columnas tiene cada una y de qué tipo. Es contrato: una fila que no cumple el esquema no entra. Se cambia con migraciones, no a mano.

Ejercicio 1.

class User {
  constructor(
    public id: number,
    public email: string,
    public is_active = true,
  ) {}
  deactivate() { this.is_active = false; }
}
class User:
    def __init__(self, id: int, email: str):
        self.id, self.email, self.is_active = id, email, True
    def deactivate(self):
        self.is_active = False
type User struct {
    ID       int
    Email    string
    IsActive bool
}
func (u *User) Deactivate() { u.IsActive = false }

Ejercicio 2. Deberías ver HTTP/1.1 200 OK y Content-Type: application/json en los headers impresos por -i.

Ejercicio 3. users.filter(u => u.is_active).map(u => u.name) — filter primero, map después. La comprehension hace ambos en una expresión.

Mini reto. La clase es el molde (la definición de qué es una tarea); el objeto es una instancia concreta (la tarea #7 que creó Ana). Un schema es clase porque define una forma que se instancia por cada request que entra; un endpoint es función porque describe una acción (recibir un request, devolver una response), no un estado que mantener.

8.12 Vocabulario técnico del capítulo

Una variable es una caja con nombre donde guardas un valor: let total = 42 guarda el 42 bajo el nombre total. Puedes leerla y cambiarla después (total = 50). const = caja que no se puede reemplazar.

El runtime es el motor que ejecuta tu código: Node.js es el runtime de JavaScript fuera del navegador; CPython es el de Python; Go compila a binario y el runtime va empaquetado dentro. Es “quien corre” lo que escribiste.

Un entorno es una instancia completa donde corre tu app con su propia config y datos: local (tu máquina), staging (réplica de prueba), producción (la real). Cada entorno tiene sus propias llaves y su propia base de datos.

Un compilador traduce código a otra forma: TypeScript → JavaScript (transpila), Go → binario (compila). Atrapa errores antes de ejecutar. tsc, esbuild, go build son compiladores.

Un CLI (Command Line Interface) es un programa que se usa escribiendo comandos en la terminal: git, npm, docker son CLIs. Lo contrario es una GUI (interfaz gráfica con botones).

La autenticación responde “¿quién eres?” — login, contraseña, token. Se confunde con autorización (“¿qué puedes hacer?”), que es la pregunta siguiente. Primero te identificas, luego te dejan o no pasar.

8.13 Lo que deberías saber hacer ahora

Si marcaste todo: puedes leer el libro entero. Lo que falte, aparecerá justo cuando el problema lo necesite.