{"schemaVersion":1,"id":"https://lucioduran.com/es/decisions/mongodb-over-postgresql","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql","title":"MongoDB over PostgreSQL for Content API","description":"The portfolio's content model is inherently heterogeneous. Work experiences carry arrays of technologies, nested duty descriptions, and optional media reference","language":"en","datePublished":null,"dateModified":null,"contentFormat":"text/markdown","content":"ADR-002accepted8 de febrero de 2024\n\n# MongoDB over PostgreSQL for Content API\n\n## Contexto\n\nThe portfolio's content model is inherently heterogeneous. Work experiences carry arrays of technologies, nested duty descriptions, and optional media references. Education entries have variable fields depending on whether they represent formal degrees, online courses, or certifications. Technologies reference categories, proficiency levels, and related experience entries. A relational model would require numerous join tables, nullable columns, and migration scripts every time the content schema evolves, which happens frequently as new sections are added to the portfolio. The data access patterns are overwhelmingly read-heavy (99%+ reads) with writes occurring only through an admin interface used by a single author.\n\n## Decisión\n\nUse MongoDB as the persistence layer for the content API. Each content type (experiences, educations, technologies, skills, duties, posts, links) maps to a dedicated collection with a flexible document schema. Documents are denormalized where practical, for example, a work experience document embeds its technology references rather than maintaining foreign keys. The API layer (Node.js/Express) performs simple collection queries with optional aggregation pipelines for grouped views (e.g., technologies grouped by category). No ORM is used; the native MongoDB driver provides direct query control.\n\n## Consecuencias\n\nPositive: Schema evolution requires zero migrations. Adding a new field to an education document is a single API update, no ALTER TABLE, no deployment, no downtime. The document model naturally represents the nested, variable-shape content that the portfolio requires. Aggregation pipelines handle grouped queries (technologies by category) without application-level data transformation. Read performance is excellent given the small dataset size (~500 documents total). Negative: No referential integrity enforcement at the database level. Orphaned references (e.g., a deleted technology still referenced by an experience) must be handled in application logic. No transactional guarantees across collections, though this is irrelevant for a single-author content system. Query optimization is limited compared to PostgreSQL's query planner for complex joins, but the access patterns don't require complex joins. The trade-off is clear: schema flexibility and development velocity outweigh relational guarantees for this use case.\n\n## Incertidumbre Calibrada\n\n## Predicciones al Momento de la Decisión\n\nExpected schema flexibility to be the decisive advantage, specifically, the ability to add new fields without migrations. Predicted the lack of referential integrity would be manageable because the single-author model makes orphaned references easy to detect manually. Assumed query complexity would remain simple (single-collection lookups, one aggregation pipeline for grouped technologies) and never require complex joins.\n\n## Resultados Medidos\n\nSchema flexibility delivered exactly as predicted. Over 20+ months, the content model evolved 15+ times (adding new fields to experiences, new collection for links, restructuring technology categories), zero migration scripts written. The aggregation pipeline for grouped technologies works flawlessly and has never needed modification. The missing referential integrity has caused exactly one issue: a deleted technology was still referenced in two experience documents, caught during a manual review three months after deletion. Query complexity remains trivial, the most complex operation is still the technology grouping pipeline. Total MongoDB hosting cost: $0 (Atlas free tier, M0 cluster).\n\n## Incógnitas al Momento de la Decisión\n\nDid not know at decision time whether the content model would stabilize or continue evolving. If it had stabilized early, PostgreSQL's rigidity would have been a non-issue and its query planner would have been a net benefit. Also unknown: whether future features would require cross-collection transactions (they haven't). The biggest unknown was Atlas free tier longevity, if MongoDB Inc. had deprecated M0 clusters, migration to a self-hosted instance or PostgreSQL would have been forced.\n\n## Clasificación de Reversibilidad\n\nPuerta de Ida y Vuelta\n\nThe API layer abstracts the database, Express route handlers return JSON regardless of the storage engine. Migrating to PostgreSQL requires: creating equivalent tables, writing a one-time data migration script, and replacing MongoDB driver calls with SQL queries in ~12 route handlers. The document-to-row mapping is straightforward for this dataset size. Estimated reversal effort: 8-12 hours. The API consumers (frontend, admin UI) would see zero interface changes.\n\n## Contra-Argumento Más Fuerte\n\nPostgreSQL with JSONB columns would have provided the same schema flexibility for heterogeneous content types while adding referential integrity, ACID transactions, and a superior query planner, all for the same cost ($0 on Supabase or Neon free tier). The PostgreSQL ecosystem also offers better tooling for data exploration (pgAdmin, built-in EXPLAIN ANALYZE). The counter-counter: JSONB queries require different mental models than native document queries, and the operational familiarity with MongoDB was a genuine productivity advantage.\n\n## Contexto Técnico\n\nStack\n\n- MongoDB\n- Node.js\n- Express\n- Native MongoDB Driver\n\nTotal Documents\n\n~500\n\nCollections\n\n8\n\nRead Write Ratio\n\n99:1\n\nAvg Query Time\n\n\\<5ms\n\nRestricciones\n\n- Single-author system\n- No cross-collection transactions needed\n- Schema changes frequent\n\n## Decisiones Relacionadas\n\n- [ADR-005accepted\n\n  Decoupled API Architecture](<https://lucioduran.com/es/decisions/decoupled-api-architecture>)\n- [ADR-008accepted\n\n  Static JSON over CMS for Decision Records](<https://lucioduran.com/es/decisions/static-json-over-cms>)\n\n[Volver a Decisiones](<https://lucioduran.com/es/decisions>)","headings":[{"level":1,"text":"MongoDB over PostgreSQL for Content API","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#title"},{"level":2,"text":"Contexto","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-context"},{"level":2,"text":"Decisión","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-decision"},{"level":2,"text":"Consecuencias","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-consequences"},{"level":2,"text":"Incertidumbre Calibrada","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-uncertainty"},{"level":2,"text":"Predicciones al Momento de la Decisión","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-predictions"},{"level":2,"text":"Resultados Medidos","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-outcomes"},{"level":2,"text":"Incógnitas al Momento de la Decisión","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-unknowns"},{"level":2,"text":"Clasificación de Reversibilidad","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-reversibility"},{"level":2,"text":"Contra-Argumento Más Fuerte","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-counterArgument"},{"level":2,"text":"Contexto Técnico","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-techContext"},{"level":2,"text":"Decisiones Relacionadas","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-relatedDecisions"}],"sections":[{"heading":"MongoDB over PostgreSQL for Content API","level":1,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql","content":"ADR-002accepted8 de febrero de 2024"},{"heading":"MongoDB over PostgreSQL for Content API","level":1,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#title","content":""},{"heading":"Contexto","level":2,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-context","content":"The portfolio's content model is inherently heterogeneous. Work experiences carry arrays of technologies, nested duty descriptions, and optional media references. Education entries have variable fields depending on whether they represent formal degrees, online courses, or certifications. Technologies reference categories, proficiency levels, and related experience entries. A relational model would require numerous join tables, nullable columns, and migration scripts every time the content schema evolves, which happens frequently as new sections are added to the portfolio. The data access patterns are overwhelmingly read-heavy (99%+ reads) with writes occurring only through an admin interface used by a single author."},{"heading":"Decisión","level":2,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-decision","content":"Use MongoDB as the persistence layer for the content API. Each content type (experiences, educations, technologies, skills, duties, posts, links) maps to a dedicated collection with a flexible document schema. Documents are denormalized where practical, for example, a work experience document embeds its technology references rather than maintaining foreign keys. The API layer (Node.js/Express) performs simple collection queries with optional aggregation pipelines for grouped views (e.g., technologies grouped by category). No ORM is used; the native MongoDB driver provides direct query control."},{"heading":"Consecuencias","level":2,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-consequences","content":"Positive: Schema evolution requires zero migrations. Adding a new field to an education document is a single API update, no ALTER TABLE, no deployment, no downtime. The document model naturally represents the nested, variable-shape content that the portfolio requires. Aggregation pipelines handle grouped queries (technologies by category) without application-level data transformation. Read performance is excellent given the small dataset size (~500 documents total). Negative: No referential integrity enforcement at the database level. Orphaned references (e.g., a deleted technology still referenced by an experience) must be handled in application logic. No transactional guarantees across collections, though this is irrelevant for a single-author content system. Query optimization is limited compared to PostgreSQL's query planner for complex joins, but the access patterns don't require complex joins. The trade-off is clear: schema flexibility and development velocity outweigh relational guarantees for this use case."},{"heading":"Incertidumbre Calibrada","level":2,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-uncertainty","content":""},{"heading":"Predicciones al Momento de la Decisión","level":2,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-predictions","content":"Expected schema flexibility to be the decisive advantage, specifically, the ability to add new fields without migrations. Predicted the lack of referential integrity would be manageable because the single-author model makes orphaned references easy to detect manually. Assumed query complexity would remain simple (single-collection lookups, one aggregation pipeline for grouped technologies) and never require complex joins."},{"heading":"Resultados Medidos","level":2,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-outcomes","content":"Schema flexibility delivered exactly as predicted. Over 20+ months, the content model evolved 15+ times (adding new fields to experiences, new collection for links, restructuring technology categories), zero migration scripts written. The aggregation pipeline for grouped technologies works flawlessly and has never needed modification. The missing referential integrity has caused exactly one issue: a deleted technology was still referenced in two experience documents, caught during a manual review three months after deletion. Query complexity remains trivial, the most complex operation is still the technology grouping pipeline. Total MongoDB hosting cost: $0 (Atlas free tier, M0 cluster)."},{"heading":"Incógnitas al Momento de la Decisión","level":2,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-unknowns","content":"Did not know at decision time whether the content model would stabilize or continue evolving. If it had stabilized early, PostgreSQL's rigidity would have been a non-issue and its query planner would have been a net benefit. Also unknown: whether future features would require cross-collection transactions (they haven't). The biggest unknown was Atlas free tier longevity, if MongoDB Inc. had deprecated M0 clusters, migration to a self-hosted instance or PostgreSQL would have been forced."},{"heading":"Clasificación de Reversibilidad","level":2,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-reversibility","content":"Puerta de Ida y Vuelta\n\nThe API layer abstracts the database, Express route handlers return JSON regardless of the storage engine. Migrating to PostgreSQL requires: creating equivalent tables, writing a one-time data migration script, and replacing MongoDB driver calls with SQL queries in ~12 route handlers. The document-to-row mapping is straightforward for this dataset size. Estimated reversal effort: 8-12 hours. The API consumers (frontend, admin UI) would see zero interface changes."},{"heading":"Contra-Argumento Más Fuerte","level":2,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-counterArgument","content":"PostgreSQL with JSONB columns would have provided the same schema flexibility for heterogeneous content types while adding referential integrity, ACID transactions, and a superior query planner, all for the same cost ($0 on Supabase or Neon free tier). The PostgreSQL ecosystem also offers better tooling for data exploration (pgAdmin, built-in EXPLAIN ANALYZE). The counter-counter: JSONB queries require different mental models than native document queries, and the operational familiarity with MongoDB was a genuine productivity advantage."},{"heading":"Contexto Técnico","level":2,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-techContext","content":"Stack\n\n- MongoDB\n- Node.js\n- Express\n- Native MongoDB Driver\n\nTotal Documents\n\n~500\n\nCollections\n\n8\n\nRead Write Ratio\n\n99:1\n\nAvg Query Time\n\n\\<5ms\n\nRestricciones\n\n- Single-author system\n- No cross-collection transactions needed\n- Schema changes frequent"},{"heading":"Decisiones Relacionadas","level":2,"url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql#decisionDetail-relatedDecisions","content":"- [ADR-005accepted\n\n  Decoupled API Architecture](<https://lucioduran.com/es/decisions/decoupled-api-architecture>)\n- [ADR-008accepted\n\n  Static JSON over CMS for Decision Records](<https://lucioduran.com/es/decisions/static-json-over-cms>)\n\n[Volver a Decisiones](<https://lucioduran.com/es/decisions>)"}],"links":[{"text":"ADR-005acceptedDecoupled API Architecture","url":"https://lucioduran.com/es/decisions/decoupled-api-architecture"},{"text":"ADR-008acceptedStatic JSON over CMS for Decision Records","url":"https://lucioduran.com/es/decisions/static-json-over-cms"},{"text":"Volver a Decisiones","url":"https://lucioduran.com/es/decisions"}],"structuredData":[{"@context":"https://schema.org/","@graph":[{"@type":"TechArticle","url":"https://lucioduran.com/es/decisions/mongodb-over-postgresql","name":"ADR-002: MongoDB over PostgreSQL for Content API","headline":"MongoDB over PostgreSQL for Content API","datePublished":"2024-02-08","author":{"@id":"https://lucioduran.com/#person"},"isPartOf":{"@id":"https://lucioduran.com/#website"},"inLanguage":"en","about":"Software Architecture Decision Record","proficiencyLevel":"Expert","dependencies":"MongoDB, Node.js, Express, Native MongoDB Driver"},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Decisiones de Arquitectura","item":"https://lucioduran.com/es/decisions"},{"@type":"ListItem","position":3,"name":"MongoDB over PostgreSQL for Content API"}]}]}],"contentHash":"c5199abee1438628033bf51b0765e31af4c58a8c1fc35c6ab8fc4df5d895add0","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/decisions/mongodb-over-postgresql","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":37017,"contentBytes":5956}}
