8 0. Fundamentos: lo que ya casi sabes
El piso mínimo para leer todo este libro — y salir sabiendo backend
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.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 TaskCreatedefine la forma válida del body - Modelos de datos:
class Taskmapea 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 escribethis(y a veces desaparece mágicamente); en Python se declaraselfcomo 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
selfes 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 = directorioDos detalles Python que confunden al principio:
- snake_case:
create_task, nocreateTask. Convención del lenguaje. - La indentación ES la sintaxis:
def/if/forterminan en:y el cuerpo va indentado — no hay{}niend.
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() equivalenteLas 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/raiseuna excepción; alguien arriba hacecatch/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
- Escribe en tu lenguaje elegido una clase/struct
Userconid,email,is_activey un métododeactivate()que la apague. - Haz
curl -i http://localhost:8000/taskscontra el servidor del Cap. 3 y lee los headers de la response — encuentra el status y elContent-Type. - 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 = Falsetype 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.