Skip to content
DevMeme
6022 of 7435
When your Postgres schema is just SERIAL plus JSONB and vibes
Databases Post #6592, on Mar 29, 2025 in TG

When your Postgres schema is just SERIAL plus JSONB and vibes

Why is this Databases meme funny?

Level 1: One Box For Everything

Imagine you have a big toy box where you keep all your toys. You don’t sort them at all — action figures, blocks, crayons, balls — everything just gets tossed into that one huge box. You also stick a number on each toy, just so you have an ID tag, but besides that, every toy and its details are jumbled together inside the box. It’s easy for you when cleaning up: you just throw the toy in the box and you’re done. No need to think about separate shelves or categories.

Now compare that to a more organized approach: normally, you might have one box for Lego pieces, another box for dolls, a shelf for books, etc. That way, when you need a certain Lego piece, you know exactly which box to look in. In the one-big-box approach, when you want that tiny red Lego, you’ll have to dig through a mountain of unrelated stuff (crayons, toy cars, teddy bears) to find it — if you can find it at all. Also, if someone else is looking for a toy and they don’t know what’s inside the box, it’s pure chaos for them to figure it out.

In this meme’s terms, the huge toy box with everything in it is like that single JSONB column holding all the data. The little number tags on each toy are like the SERIAL id – they give each entry a unique identifier, but they don’t organize the content itself. When the tweet says “only need 2” types, it’s joking that some people design a database table with just those two things: an ID (like the toy’s number tag) and one big field that contains everything else about that item in a messy bundle. The phrase “and vibes” is a funny way of saying “and whatever happens, happens” – basically running on hope or feeling rather than a clear plan.

So why is that silly? Because not organizing your stuff (or your data) can lead to trouble. It’s easy at first (just throw it all in one place, done!), but later on it’s hard to find things, hard to ensure everything’s correct, and hard to trust what you have. The meme makes us laugh by showing a ridiculously minimalistic approach — like saying “Who needs proper sections or labels? Let’s just wing it!” Anyone who’s ever had to clean up a messy room or search for a lost toy in a giant pile can feel why that’s not ideal. It’s funny in the comic sense: we picture the absurdity of a database that’s basically just one big box of JSON data, held together with an ID and good vibes.

Level 2: JSON All The Things

At this level, let’s break down what’s happening in that tweet and code snippet in straightforward terms. PostgreSQL (often just called Postgres) is a popular SQL database that, unlike some simpler databases, supports lots of different data types for table columns. For example, you can have INTEGER for whole numbers, VARCHAR (or TEXT) for text, BOOLEAN for true/false values, DATE for dates, JSONB for JSON data, and many more. Each column in a table is usually assigned the type that best fits the data it’s going to store, which helps keep data consistent and queries efficient.

Now, the tweet jokes that despite this richness, some people act like you only ever need two data types in Postgres. Which two? The example shows them: SERIAL and JSONB. Let’s explain those:

  • SERIAL: This is essentially a shortcut in Postgres to create an auto-incrementing integer column. If you declare id SERIAL PRIMARY KEY, you’re telling the database, “Make a column named id, type integer, and automatically give it the next number for each new row, and also treat it as the primary key (unique identifier) for the table.” Every time you insert a new row without specifying an id, Postgres will generate the next number (1, 2, 3, …) automatically. It’s a convenient way to get unique IDs without thinking too hard. In newer Postgres versions, there’s a more standard way to do this (GENERATED AS IDENTITY), but SERIAL is still very commonly seen in older code and examples.

  • JSONB: This stands for “JSON Binary”. JSON is a data format – basically text structured like {"key": "value"} pairs, possibly nested, which is widely used in web APIs and config files because it’s flexible and human-readable. In Postgres, the JSONB type lets you store an entire JSON object as a single value in a table column. The “B” means it’s stored in a binary form that’s efficient for searching and indexing (better than just plain text JSON). So a JSONB column can hold complex data – almost like a mini-document – within one table cell. For instance, you could store something like {"name": "Coffee Mug", "price": 7.99, "colors": ["red", "green"]} all in one JSONB field of a single row.

Now, the code snippet from the meme (stylized with curly braces in the image, but typically you’d use parentheses in real SQL) looks like this:

CREATE TABLE product (
    id SERIAL PRIMARY KEY,
    data JSONB DEFAULT '{}'
);

This means we’re creating a new table named product with two columns:

  • id: an auto-incrementing integer, which will serve as the primary key.
  • data: a JSONB column that by default starts as an empty JSON object {} if we don’t provide anything.

The joke is that the table has only these two columns. Normally, for a product, you might expect columns like name TEXT, price DECIMAL, category TEXT, etc., each with an appropriate type. Here, instead of defining those, they’re implying all that detail will just go into the data JSONB column. So one product’s row might have data = {"name": "Coffee Mug", "price": 7.99, "category": "Kitchenware"}, and another product row might have data = {"name": "T-Shirt", "size": "L", "color": "Blue"}, and so on. The JSON structure can be different for each row if needed – it’s super flexible because the database isn’t enforcing a schema on what’s inside data.

This approach is sometimes called using a schemaless design (even though Postgres is a schema-based database). “Schemaless” means you’re not rigidly defining the shape of your data upfront; you can just throw in any keys and values as you go. It’s what NoSQL databases (like MongoDB) do – they don’t require a predefined set of columns; each “document” (equivalent to a row) can have its own shape. Postgres with JSONB is letting you imitate that within a traditional SQL database.

So why do people do this, and why is it humorous?

Why people do it: It can be convenient. Imagine you’re moving fast or you expect your requirements to change frequently. Defining a bunch of columns and then altering the table every time there’s a new attribute is work (and downtime, and migrations…). With a JSONB column, if tomorrow you need to start storing a new field for some products (say, a discountPercentage), you just start adding it in the JSON – no database migration required. It’s very flexible and can save development time in the short run. It’s like having a big box where you can toss any kind of data in without asking for permission.

Why it’s funny / risky: By only using an id and a JSONB, you sidestep all the benefits of a structured schema. You lose data integrity guarantees. For example, if every product is supposed to have a “name” and “price”, a proper schema would have NOT NULL constraints on those columns or at least define them. In the JSON approach, there’s nothing stopping one row’s JSON from missing a name or having price as "seven" (a string) instead of a number. The database won’t complain because from its perspective, data is just a JSON blob – it doesn’t look inside. All the responsibility for handling that moves to the application code or whoever queries the data. It’s easy for mistakes to slip in.

Also, working with JSON data in SQL, while supported, is a bit more complex. Instead of a simple SELECT name FROM product WHERE price < 10, you’d do something like:

SELECT data->>'name' AS name
FROM product
WHERE (data->>'price')::numeric < 10;

Here data->>'name' is a Postgres syntax to retrieve the value of the "name" key from the JSON as text, and (data->>'price')::numeric means “get the price value from the JSON and cast it to a number so we can compare it”. It works, but it’s not as straightforward as normal columns. And if someone typo’s "Price" with a capital P in one JSON entry, that row’s name or price won’t match the query because JSON keys are case-sensitive and not standardized by the DB. These are the kinds of burdens you take on when you use JSON for everything.

The meme specifically highlights tech debt in the making. Technical debt is a term for when you take a shortcut in tech that saves time now but will likely cause trouble later (like debt you’ll have to “pay back” with interest). By saying “you really only need 2” types, the tweet is facetiously advising a shortcut that most in-the-know devs recognize as a bad habit except in very particular cases. In other words, it’s poking fun at the notion that you can ignore proper database design principles (like normalization, choosing the right data types, designing schemas) and get away with it scot-free. It’s the kind of joke where if you’re a junior dev who hasn’t seen the fallout, you might not realize it’s sardonic – but if you’re a senior who’s had to untangle a mess of JSON data in a supposed SQL database, you’re probably smirking or shaking your head.

To put it simply, the tweet reduces the entirety of Postgres schema design to “just use an id and a JSONB for everything”. It’s funny because it’s so oversimplified that it rings true about a certain mindset. It encapsulates a scenario many find cringe-worthy yet familiar. The tags like jsonb_overuse or lazy_schema perfectly describe what’s going on: it’s an overuse of JSONB due to laziness or desire for quick-and-easy schema changes. And while JSON is awesome in many contexts, using it as a dumping ground for all data in a relational DB is usually a sign that you’re avoiding the hard (but necessary) work of modeling your data properly. The tweet’s dark-humor punchline is that some developers do exactly that and then claim everything is fine — running on nothing but {} and vibes.

Level 3: Serial Offender

“Postgres has lots of types, but you really only need 2.”

Experienced developers chuckle (and maybe groan) at this tweet because it captures a real-world anti-pattern in a snappy one-liner. The tweet’s author quips that despite Postgres offering a cornucopia of column types (from INTEGER to BOOLEAN to TEXT to UUID and beyond), some developers act like only two types matter: an auto-incrementing SERIAL for a primary key, and JSONB for everything else. It’s humor by exaggeration, but not by much – many of us have stumbled upon a table design exactly like product(id SERIAL PRIMARY KEY, data JSONB DEFAULT '{}') in the wild.

This combination – an id plus a schemaless JSON – is essentially treating PostgreSQL like a simple key-value store or a document database. The id (often just an integer that increments for each row) is the key, and the JSONB column holds an entire blob of data (the value) that could be anything: a product’s name, price, attributes, nested sub-objects, you name it. It’s Postgres meets MongoDB: NoSQL inside SQL. We’re using a relational database, but we’re rebelling against the relational paradigm by cramming an entire object into one field. The phrase “and vibes” jokingly implies that beyond those two defined fields, everything else about the data is just handled by feel or convention, with no formal schema – in other words, “we have an id, we have a JSON, and the rest is just… vibes (trust us)”. It’s a tongue-in-cheek way to say logic and structure have left the chat.

Why is this funny to seasoned devs? Because it’s relatable – many have either done this in a rush or inherited a system that did. It’s like a dirty little secret in backend development: when you’re not sure about your data model or you’re too lazy (or pressured) to design it properly, you just dump everything into a JSON. Need a new field? Add it to the JSON – no migrations, no schema changes, no hassle… until later. The technical debt from this shortcut accrues silently. Down the road, queries become more complex, data inconsistencies slip in (since the database isn’t enforcing any rules inside that JSON), and everyone who touches the code has to remember what structure the JSON is supposed to have. The joke lands because it hints at that impending chaos with a casual shrug, as if to say, “Eh, who needs proper database design? Just use JSON and wing it.”

Consider a real scenario: you have a product table and you’re not certain what attributes each product will have, since it varies by category. One engineer proposes, “Let’s just use a JSONB field to store an arbitrary blob of attributes for each product.” In the short term, this is super convenient – you can store { "color": "red", "size": "M" } for one item and { "expiry": "2025-12-31" } for another, no fuss about altering the table for new columns. It’s very flexible. But an experienced engineer reading this design might raise an eyebrow. They know that six months later, the business will ask questions like “Give me all products of color = red and size = M” or “Which products expire by end of 2025?”. At that point, the team will either write funky queries digging into data ->> 'color' and data ->> 'expiry' (JSON extraction) or, worse, they’ll have to slurp all that JSON out in application code and filter there. Performance can tank if you’re not careful, because the database can’t easily use normal indexes inside the JSON without special setup. And heaven forbid if one JSON entry used "colour" instead of "color" – the database won’t know, but your query will silently miss those items. Data integrity has left the building.

The meme is mocking the overuse of JSONB. Postgres veterans know that JSONB is a powerful feature, but it’s best used sparingly, for cases where data truly doesn’t fit a fixed schema or for prototyping. Abusing it as a crutch for all data leads to a maintainability nightmare. It’s basically saying “I don’t want to think about schema design, I’ll just throw everything into JSONB.” Sure, you avoid writing ALTER TABLE statements today, but you create a pile of technical debt technical debt that your future self or team will have to pay off. It’s funny because it’s true: plenty of start-ups and projects begin with this YOLO schema approach – it’s all “JSON and vibes” until things get complicated.

There’s also an implicit jab at the mindset of some developers. In an era of microservices and rapid iteration, the attitude can be “we’ll structure it later, right now just ship it.” The relatable developer experience hidden here is the on-call nightmare or migration from hell when later finally arrives. Picture a veteran DBA’s face when they see a table with 50 JSONB columns, or one JSONB that holds an entire denormalized world – it’s a mix of horror and déjà vu. The tweet’s humor is that it pretends this is fine, even optimal (“you really only need 2” kinds of types) – a sarcastic endorsement of a bad habit. It resonates with seniors because we detect the irony immediately: Yeah, you can do that, and it might work for a while, but you’re gonna regret it.

In short, at the senior level, the meme is a wry commentary on schema design shortcuts. It highlights the disconnect between best practice (use the rich types and design a proper schema) and what sometimes happens under deadline pressure (just dump it in JSON, we’ll figure it out later). The humor comes from recognizing this scenario and the faux-confidence of stating it like a pro tip. It’s like saying “This is the way, trust me” – with the unspoken punchline that this way is likely to backfire spectacularly down the line. Seasoned devs laugh (or smirk) because they’ve lived the consequences, making the meme equal parts funny and painfully accurate.

Level 4: Beyond First Normal Form

In classical relational theory, every table is supposed to adhere to First Normal Form (1NF), which (among other things) means each column holds a single, indivisible value – no repeating groups or nested records. By shoving everything except an ID into one JSONB blob, we’re effectively breaking 1NF and returning to a pre-relational, hierarchical data design right inside our SQL database. It’s as if we’ve smuggled a NoSQL document store into a relational schema. This undermines the theoretical guarantees of the relational model: the database no longer truly "knows" the structure of our data beyond that single id.

Why does this matter? Under the hood, PostgreSQL’s query planner and optimizer rely on data types, constraints, and statistics about each column’s distribution to plan efficient queries. When most of your data lives in a schemaless JSONB column, the database has far less insight. Imagine asking the engine to find all products cheaper than $100 – if price is buried in JSON, the engine can’t use a simple range index on a numeric column. Instead, it may have to scan through each JSONB value, parse it, and then compare. We’ve sacrificed the elegant set-based logic of SQL for brute-force searching inside a blob. Essentially, the table stops behaving like a relational set and more like a collection of independent semi-structured documents.

There’s also a type theory aspect here. PostgreSQL usually enforces strong types: for example, an INTEGER column cannot hold text, and a DATE column can’t hold an arbitrary string. This is a form of integrity (and a simple form of formal verification on your data). By using JSONB for everything, we downgrade to a single catch-all type – effectively a variant that can contain any type or structure without constraint. We’ve lost the safety that comes from the database’s type system. The joke is highlighting a perverse extreme: having a rich palette of data types (numeric, boolean, text, timestamp, etc.) defined by decades of computer science theory, and then blissfully ignoring all of it in favor of just two generic ones. It’s a bit like designing a sophisticated programming language with classes and types, but writing all your code with one giant Object type – sure, you can do it, but it defeats the purpose of the language’s design.

Historically, this is a fascinating full-circle moment. The relational model (proposed by E.F. Codd in 1970) was a revolution against the then-dominant hierarchical and network databases, which required lots of hand-holding to traverse linked records. Relational databases introduced abstraction with tables and a declarative query language (SQL), freeing us from worrying about low-level data traversal. Ironically, JSONB brings a hierarchical container back into our nicely organized tables. And why did PostgreSQL add JSONB? Largely due to the rise of NoSQL document stores in the 2010s – developers loved the flexibility of JSON documents. PostgreSQL, being the over-achiever of relational databases, said “Sure, we can do that too!” by introducing a binary JSON type with indexing capabilities. In theory, this feature is meant to be used judiciously – for semi-structured data or extensibility when you can’t define a rigid schema up front. But the meme jokes that some devs took this feature and ran wild with it, using it as an excuse to ignore normal forms and design principles entirely. It’s a textbook example of how a powerful tool can be abused, creating a scenario that relational database theorists would find heretical.

In summary, at this deepest level we see the humor as an almost absurdist inversion of database theory: a relational DB’s strength is its structured schema and type enforcement, yet here it’s being used in the most un-structured way possible. The fundamental trade-offs (schema safety vs. schema flexibility) are at play. The meme tickles the part of a database expert’s brain that remembers the old academic wisdom and sees it casually tossed aside with "and vibes". It’s both comical and cringe-inducing: comical because it’s deliberately exaggerated, and cringe because, deep down, it violates the elegant principles that relational databases are built upon.

Description

Screenshot of a dark-mode tweet from a user named “Tom” (@tomdoes_tech). The tweet text reads: “Postgres has lots of types, but you really only need 2”. Below the tweet is a stylised code block on a charcoal background with colourful syntax highlighting: CREATE TABLE product { id SERIAL PRIMARY KEY, data JSONB DEFAULT '{}', } The humour comes from declaring that the entire richness of PostgreSQL’s type system is reduced to just an auto-incrementing integer (SERIAL) and a schemaless blob (JSONB), implicitly mocking developers who dodge proper relational modelling and lean on JSONB for everything, thereby piling up technical debt and undermining data integrity

Comments

10
Anonymous ★ Top Pick Sure, it ships to prod fast - right up until the BI team asks for a LEFT JOIN on a key buried three levels deep in that JSONB column
  1. Anonymous ★ Top Pick

    Sure, it ships to prod fast - right up until the BI team asks for a LEFT JOIN on a key buried three levels deep in that JSONB column

  2. Anonymous

    After 15 years of arguing about normalization forms and foreign key constraints, we've finally achieved enlightenment: every table is just a key-value store with extra steps, and PostgreSQL's JSONB is just MongoDB with ACID compliance and better query planning

  3. Anonymous

    Ah yes, the 'two types' approach to PostgreSQL: one for when you need to know what something is (SERIAL), and one for when you've given up trying to predict the future (JSONB). It's the database equivalent of 'I'll organize this later' - except 'later' never comes, and now your entire product catalog lives in a JSON blob with no schema validation. Sure, it makes migrations trivial, but wait until you're writing WHERE clauses that look like 'data->>'nested'->'property'->>'value' = 'something' and your query planner is crying. This is what happens when you let a startup's 'move fast' mentality design your data model - you end up with a relational database cosplaying as MongoDB, and your DBA is somewhere writing a 47-slide deck titled 'Why We Need to Talk About Normalization.'

  4. Anonymous

    Postgres: 50+ types for the DBA, SERIAL + JSONB for the dev who ships features faster than schemas evolve

  5. Anonymous

    Nothing says relational like id SERIAL and a 300KB JSONB - congrats, you built a document store with ACID invoices and zero foreign keys

  6. Anonymous

    Nothing says “future-proof architecture” like id SERIAL and a JSONB column named data - right up until analytics asks for a JOIN and you realize you’ve reinvented EAV with a GIN index and quarterly backfills

  7. @WhiteBeef 1y

    Orm? Nooooo I have better idea:

  8. @fadhil_riyanto 1y

    "World's Most Advanced Open Source Relational Database"

  9. @slyveek 1y

    Bro invented mongodb

  10. @sometgirldotonline 1y

    she SeeQooL until I json on her db

Use J and K for navigation