<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[GreenWorld]]></title><description><![CDATA[GreenWorld]]></description><link>https://surajsrggupta.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>GreenWorld</title><link>https://surajsrggupta.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 10 Sep 2026 19:52:42 GMT</lastBuildDate><atom:link href="https://surajsrggupta.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[PostgreSQL vs MongoDB: The Real Difference Between SQL and NoSQL]]></title><description><![CDATA[Choosing a database is one of the bigger decisions in backend development, and this exact confusion tends to show up the moment a new project starts. Should I go with PostgreSQL, or should I go with M]]></description><link>https://surajsrggupta.hashnode.dev/postgresql-vs-mongodb-the-real-difference-between-sql-and-nosql</link><guid isPermaLink="true">https://surajsrggupta.hashnode.dev/postgresql-vs-mongodb-the-real-difference-between-sql-and-nosql</guid><category><![CDATA[postgres]]></category><category><![CDATA[sq]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[SQL]]></category><category><![CDATA[database]]></category><dc:creator><![CDATA[Mr. S. Gupta]]></dc:creator><pubDate>Fri, 04 Sep 2026 10:06:57 GMT</pubDate><content:encoded><![CDATA[<p>Choosing a database is one of the bigger decisions in backend development, and this exact confusion tends to show up the moment a new project starts. Should I go with PostgreSQL, or should I go with MongoDB?</p>
<p>Some people will tell you SQL is old and NoSQL is the modern way to go. Others will insist MongoDB scales better and PostgreSQL just can't keep up. Both of these takes are incomplete, and honestly a bit misleading.</p>
<p>The truth is that SQL and NoSQL are two different philosophies, each built to solve a different kind of problem. There's no universally "best" database out there. Once you actually understand what each of these really is, deciding between them stops being a guessing game.</p>
<h2>What a database is actually for</h2>
<p>A database's job is genuinely simple, it stores your application's data. If you're building an e-commerce app, you need somewhere to keep users, products, orders, payments, and reviews. All of that data ends up in a database one way or another, the only real question is how it gets organized once it's there.</p>
<h2>What a SQL database actually is</h2>
<p>SQL stands for Structured Query Language, and SQL databases are also called relational databases. PostgreSQL, MySQL, Oracle Database, and Microsoft SQL Server are all common examples. In all of them, data lives inside tables.</p>
<p>Take a simple users table.</p>
<pre><code class="language-plaintext">Users Table

+----+---------+-------------------+
| id | name    | email             |
+----+---------+-------------------+
| 1  | Rahul   | rahul@test.com    |
| 2  | Aman    | aman@test.com     |
+----+---------+-------------------+
</code></pre>
<p>And an orders table.</p>
<pre><code class="language-plaintext">Orders Table

+----+---------+--------+
| id | user_id | amount |
+----+---------+--------+
| 1  | 1       | 500    |
| 2  | 1       | 900    |
| 3  | 2       | 300    |
+----+---------+--------+
</code></pre>
<p>There's a relationship here, one user can have many orders, and that's exactly why these are called relational databases.</p>
<p>PostgreSQL is simply a relational SQL database. Data lives in tables, relationships are explicit, you write actual SQL to interact with it, and you get strong consistency guarantees.</p>
<pre><code class="language-sql">SELECT *
FROM users
WHERE id = 1;
</code></pre>
<p>This is why PostgreSQL shows up so often in SaaS applications, banking systems, enterprise software, and e-commerce platforms.</p>
<h2>What MongoDB actually is</h2>
<p>MongoDB is a NoSQL database, and NoSQL originally just meant "not only SQL." It doesn't mean NoSQL databases are somehow better than SQL ones, it just means they organize data differently than tables. MongoDB specifically is a document based database.</p>
<p>Where SQL structures things as tables full of rows and columns, MongoDB structures things as collections full of documents, and those documents look a lot like JSON.</p>
<pre><code class="language-json">{
 "_id": 1,
 "name": "Rahul",
 "email": "rahul@test.com",
 "orders": [
   {
    "product": "Laptop",
    "price": 50000
   }
 ]
}
</code></pre>
<p>It looks like JSON, but technically it isn't. MongoDB internally stores things as BSON, which is basically binary JSON.</p>
<p>One thing worth clearing up here, MongoDB doesn't just store raw JSON files. The documents look JSON-like, but under the hood the database engine still handles indexing, query processing, and storage optimization properly. It's a full database engine, not just a glorified file store.</p>
<h2>The two philosophies think about data differently</h2>
<p>SQL's whole approach is, keep your data separated across different tables and connect them through relationships. This is called normalization. In an e-commerce app, that means users, orders, and products all sit in their own tables, linked together through relations.</p>
<p>MongoDB flips that idea around, whatever data you tend to access together, store it together. You could call this denormalization.</p>
<p>Think of an Instagram style profile page as a real example. A user needs their name, bio, profile picture, and recent posts. In MongoDB, all of that can live in one single document.</p>
<pre><code class="language-json">{
"name": "Rahul",
"bio": "Developer",
"posts": [
  {
   "title": "My first post"
  }
]
}
</code></pre>
<p>In PostgreSQL, that same data would be split, a users table and a separate posts table, joined together to fetch the full picture.</p>
<pre><code class="language-sql">SELECT *
FROM users
JOIN posts
ON users.id = posts.user_id;
</code></pre>
<p>A common myth is that MongoDB doesn't support relationships at all. It does, MongoDB's design just doesn't center around joins the way relational databases do. You can absolutely store something like a <code>userId</code> field to represent a relationship yourself, the database just won't manage that relationship as efficiently or natively as SQL does.</p>
<p>Firestore falls into this same document database category, collections holding documents, documents holding fields. The structure looks a lot like MongoDB's, but they aren't the same product. Both are NoSQL, both are document based, both store JSON-like data, but MongoDB is a general purpose database built for complex querying and works fine self hosted or on managed cloud, while Firestore lives inside Google's Firebase ecosystem, leans heavily into real-time applications, and follows a serverless approach.</p>
<h2>The biggest real difference: joins vs embedding</h2>
<p>This is really the core conceptual split between SQL and NoSQL, they organize data in fundamentally different ways.</p>
<p>Say you're building an e-commerce app with users, orders, and products. In PostgreSQL, the structure looks like this.</p>
<pre><code class="language-plaintext">Users

id
name
email

Products

id
name
price

Orders

id
user_id
product_id
quantity
</code></pre>
<p>Every entity gets its own table, because each one genuinely serves a different purpose. Pulling Rahul's orders means writing something like this.</p>
<pre><code class="language-sql">SELECT
users.name,
products.name,
orders.quantity
FROM users
JOIN orders
ON users.id = orders.user_id
JOIN products
ON products.id = orders.product_id
WHERE users.id = 1;
</code></pre>
<p>The tables are connected here, and that connecting operation is what a join actually is.</p>
<p>In MongoDB, that same data can live inside one document.</p>
<pre><code class="language-json">{
 "_id": 1,
 "name": "Rahul",
 "orders": [
  {
   "product": "Laptop",
   "quantity": 1
  },
  {
   "product": "Mouse",
   "quantity": 2
  }
 ]
}
</code></pre>
<p>All of Rahul's orders are already sitting right there, no joins needed at all.</p>
<p>Which one's better really just depends on the situation. A banking system deals with accounts, transactions, and balances, data where a mistake is genuinely expensive, and it needs strong consistency, real transactions, and clean relationships, which makes PostgreSQL a strong pick there. On the other hand, something like an Instagram feed, where likes, comments, images, and metadata are all accessed together constantly, tends to feel a lot more natural as a document model.</p>
<p>Both approaches come with their own trade-offs too. In PostgreSQL, if joins start getting genuinely complex, users, orders, payments, products, and reviews all tangled together, queries can get messy fast. In MongoDB, if the same piece of data ends up duplicated across many documents, say a laptop's price repeated in several different orders, and that price ever changes, you could end up needing to update multiple documents just to keep things consistent, which turns into a real maintenance headache.</p>
<p>A chat app like WhatsApp is a good example of where documents feel natural, messages within a conversation group together on their own.</p>
<pre><code class="language-json">{
"conversationId": 100,
"messages": [
 {
  "from": "Rahul",
  "text": "Hello"
 },
 {
  "from": "Aman",
  "text": "Hi"
 }
]
}
</code></pre>
<h2>Transactions, where SQL genuinely shines</h2>
<p>A transaction means bundling multiple database operations into one single unit, either everything happens, or nothing does.</p>
<p>A bank transfer is the classic way to think about this. Deducting 1000 rupees from Rahul's account and adding 1000 to Aman's account both need to happen together. If the first step succeeds and the second one fails, that's a genuinely serious problem.</p>
<pre><code class="language-sql">BEGIN;

UPDATE accounts
SET balance = balance - 1000
WHERE id = 1;

UPDATE accounts
SET balance = balance + 1000
WHERE id = 2;

COMMIT;
</code></pre>
<p>If something goes wrong partway through, running <code>ROLLBACK</code> reverts the database back to where it was before anything happened.</p>
<p>Relational databases lean heavily on a concept called ACID here. Atomicity means either the whole operation completes or none of it does. Consistency means the database always stays in a valid state. Isolation means multiple transactions running at once won't incorrectly interfere with each other. And durability means once data is saved, it isn't going anywhere.</p>
<p>MongoDB does support transactions too, which is worth clearing up because a lot of people assume it doesn't. It does, it's just that in relational databases, transactions and relationships are baked into the core design, whereas in MongoDB, document based operations are the more natural, common way of working.</p>
<h2>The truth about scaling</h2>
<p>One line that gets thrown around a lot is "MongoDB scales better." That statement is incomplete on its own. Scaling actually comes in two flavors, vertical scaling, which just means making a server more powerful (say, going from 8GB of RAM to 64GB), and horizontal scaling, which means adding more servers instead of a bigger one.</p>
<p>MongoDB's design is genuinely friendly toward distributed systems, supporting things like sharding and distributed storage well for large datasets. But PostgreSQL scales too, through read replicas, partitioning, caching, and query optimization, and today it's genuinely used in plenty of large scale applications without issue.</p>
<p>Performance comparisons follow a similar pattern. Fetching a simple user profile can be equally fast in either database. Complex relational queries, like pulling total purchases, last order date, and average order value per customer, tend to be naturally strong in SQL. And flexible, changing data, like a user's settings object, tends to feel more natural in MongoDB.</p>
<p>The schema approach also differs quite a bit. SQL defines its schema upfront, and the structure stays fixed.</p>
<pre><code class="language-sql">CREATE TABLE users(
 id INT,
 name TEXT,
 email TEXT
);
</code></pre>
<p>MongoDB documents are flexible by nature, one user might just have a <code>name</code> field, another might have <code>name</code>, <code>age</code>, and <code>city</code>, and both are perfectly valid.</p>
<p>That flexibility isn't automatically a good thing though. If a hundred developers are all saving data with slightly different shapes, some using <code>name</code>, others using <code>username</code>, things get genuinely hard to maintain over time. This is exactly where SQL's fixed schema becomes an advantage, the database itself enforces rules, like making an email field <code>NOT NULL</code> so incomplete or invalid data can never sneak in.</p>
<h2>What this actually looks like in a real project</h2>
<p>Picture a full e-commerce application with users, products, cart, orders, payments, and inventory.</p>
<p>In PostgreSQL, every entity gets its own table, users, products, orders, order items, and payments, all connected through relationships.</p>
<p>In MongoDB, that same data might live inside a single document like this.</p>
<pre><code class="language-json">{
 "_id": 101,
 "customer": "Rahul",
 "orders": [
  {
   "orderId": 500,
   "items": [
    {
     "product": "Laptop",
     "price": 50000
    }
   ],
   "payment": {
    "status": "paid"
   }
  }
 ]
}
</code></pre>
<p>Both approaches genuinely work in real projects, it just comes down to which trade-offs you're more willing to live with.</p>
<p>Firestore and MongoDB also structure things a little differently. Firestore goes collections holding documents holding fields, while MongoDB adds one more layer, a database holding collections, holding documents, holding fields. Firestore's whole focus is real-time updates, mobile apps, and serverless development, think a chat app where a message sent by one user instantly shows up for another. MongoDB is more of a general purpose tool, better suited for backend APIs, large applications, and flexible data models overall.</p>
<p>Querying differs too. Firestore's queries are intentionally limited, which works great for simple filtering, while MongoDB supports much more complex querying, range filters, aggregations, and a lot more besides.</p>
<h2>Can MongoDB actually replace PostgreSQL</h2>
<p>This question comes up constantly, and the honest answer is, sometimes, but definitely not everywhere.</p>
<p>MongoDB tends to make more sense when your data's structure keeps changing, like a CMS where today you only need a title and description, and tomorrow you're adding video and metadata fields too. It also makes sense when a document naturally represents one complete, self contained unit, like a product catalog entry with its own specifications baked in.</p>
<p>PostgreSQL tends to make more sense when relationships between your data are genuinely strong, like the chain from customer to orders to payments to invoices. It's also the better call when accuracy is truly critical, think finance, healthcare, or enterprise systems, or when you need serious reporting capability, like last quarter's revenue broken down by customer segment, something SQL handles naturally well.</p>
<p>If you're building a fairly standard SaaS product, authentication, a dashboard, payments, users, teams, PostgreSQL is a safe default, mainly because most SaaS data is fundamentally relational underneath. But if you're building a chat app, a real-time feed, or a flexible content platform, MongoDB can genuinely be the better fit.</p>
<p>It's also worth knowing that modern applications often go hybrid anyway. Main application data sitting in PostgreSQL, analytics data in MongoDB, and a cache layer in Redis, using a different tool for a different problem is completely normal in practice.</p>
<h2>So how do you actually decide</h2>
<p>The wrong question to ask when picking a database is which one's more popular, or which one's newer technology. The right question is, what does my application's data actually look like, and how will it actually be used?</p>
<p>If there are strong relationships in your data, like the chain from customer to orders to payments in e-commerce, or account to transactions in banking, or employee to department in an HR system, SQL databases are the strong choice, and PostgreSQL fits naturally.</p>
<p>If your application's structure keeps shifting, like a content platform where new fields get added constantly, document databases like MongoDB become genuinely convenient.</p>
<p>If you need complex queries, like total revenue, average order value, and purchase frequency for premium customers over the last six months, relational databases are naturally the stronger tool.</p>
<pre><code class="language-sql">SELECT
customer_id,
SUM(amount),
AVG(amount)
FROM orders
GROUP BY customer_id;
</code></pre>
<p>And if real-time updates genuinely matter, chat, live location tracking, multiplayer experiences, collaboration tools, something like Firebase Firestore becomes useful, where one user's edit shows up instantly for everyone else watching.</p>
<p>Put side by side, PostgreSQL is best suited for SaaS applications, banking systems, e-commerce, ERP, and enterprise software, with real strengths in relationships, transactions, complex queries, and data integrity, though schema changes need to be managed carefully. MongoDB is best suited for flexible data, content systems, large document heavy data, and rapid development, with strengths in flexible schemas, the document model, and easy horizontal scaling, though complex relationships need extra planning upfront. Firestore is best suited for mobile apps, real-time applications, and anything living in the Firebase ecosystem, with strengths in real-time updates, easy setup, and a genuinely serverless experience, though complex querying has real limitations and cost planning matters more here than people expect.</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>PostgreSQL</th>
<th>MongoDB</th>
<th>Firestore</th>
</tr>
</thead>
<tbody><tr>
<td>Type</td>
<td>SQL</td>
<td>NoSQL Document</td>
<td>NoSQL Document</td>
</tr>
<tr>
<td>Data Model</td>
<td>Tables</td>
<td>Documents</td>
<td>Documents</td>
</tr>
<tr>
<td>Relations</td>
<td>Excellent</td>
<td>Limited / not natural</td>
<td>Limited</td>
</tr>
<tr>
<td>Transactions</td>
<td>Excellent</td>
<td>Supported</td>
<td>Supported</td>
</tr>
<tr>
<td>Flexible Schema</td>
<td>Medium</td>
<td>Excellent</td>
<td>Excellent</td>
</tr>
<tr>
<td>Complex Queries</td>
<td>Excellent</td>
<td>Good</td>
<td>Limited</td>
</tr>
<tr>
<td>Real-time</td>
<td>Extra setup</td>
<td>Possible</td>
<td>Excellent</td>
</tr>
<tr>
<td>Learning Value</td>
<td>Very High</td>
<td>High</td>
<td>Medium</td>
</tr>
</tbody></table>
<p>A misconception worth putting to rest, SQL isn't outdated just because it's old. Being old and being outdated are two different things entirely, HTTP is old too, and it's still the backbone of the entire internet. PostgreSQL today comes with genuinely modern capabilities, JSON support, full text search, and a long list of extensions. And the idea that NoSQL is "the future" while SQL fades out doesn't really hold up either, both are going to matter for a long time, and most real world systems actually use them together. A food delivery app is a good example, users and orders sitting in PostgreSQL, live driver location in Redis or some other NoSQL store, and analytics living in a separate data warehouse entirely.</p>
<p>If you're learning backend development, a reasonable order to follow looks like this. Start with PostgreSQL and SQL first, since the fundamentals, tables, relationships, constraints, transactions, all become genuinely clear there. Then pick up MongoDB's basics, documents, collections, embedding, and referencing. After that, learn an ORM like Prisma or Drizzle, so you understand what problem an ORM is actually solving and how it makes database access easier day to day.</p>
<p>A genuinely strong backend developer doesn't say "I only use MongoDB" or "I only use PostgreSQL." They say, "I know which database fits which problem."</p>
<p>Choosing a database was never really about chasing whatever's trending. It's about actually understanding what your application needs. If you need strong relationships, accurate transactions, and serious reporting, PostgreSQL is a strong choice. If you need flexible documents, rapid changes, and document based data, MongoDB is genuinely useful. And if you need a real-time mobile experience, Firebase's ecosystem, and fast development, Firestore is a solid option. An experienced developer never treats a database like a religion, they look at the actual problem first, and only then pick the tool that fits it.</p>
]]></content:encoded></item><item><title><![CDATA[How Express Actually Talks to a Database: From HTTP Request to SQL Query]]></title><description><![CDATA[When beginners start learning backend development, they usually pick up things one at a time and separately, Express routes here, controllers there, middleware somewhere else, then databases, then Pri]]></description><link>https://surajsrggupta.hashnode.dev/how-express-actually-talks-to-a-database-from-http-request-to-sql-query</link><guid isPermaLink="true">https://surajsrggupta.hashnode.dev/how-express-actually-talks-to-a-database-from-http-request-to-sql-query</guid><category><![CDATA[SQL]]></category><category><![CDATA[Express]]></category><category><![CDATA[MERN Stack]]></category><category><![CDATA[Dat]]></category><category><![CDATA[prisma]]></category><dc:creator><![CDATA[Mr. S. Gupta]]></dc:creator><pubDate>Sun, 30 Aug 2026 14:08:01 GMT</pubDate><content:encoded><![CDATA[<p>When beginners start learning backend development, they usually pick up things one at a time and separately, Express routes here, controllers there, middleware somewhere else, then databases, then Prisma, then authentication. Each piece makes sense on its own.</p>
<p>But one question tends to linger even after all that. When a user clicks something on a website, what's actually happening behind the scenes, step by step?</p>
<p>Say someone opens an e-commerce site and clicks "Show my orders." What happens right after that click? Does Express talk to the database directly? Does Prisma just handle everything on its own? Where exactly does authentication slot in, and where does the actual SQL query get executed? Once this full flow actually clicks, backend development stops feeling like a pile of disconnected pieces.</p>
<h2>The big picture</h2>
<p>A modern backend app generally looks something like this.</p>
<pre><code class="language-plaintext">Frontend Application
        |
        v
HTTP Request
        |
        v
Express Server
        |
        v
Routes
        |
        v
Controllers
        |
        v
Services
        |
        v
Database Layer
        |
        v
PostgreSQL Database
</code></pre>
<p>Each layer here has its own job, so let's walk through them one at a time.</p>
<p><strong>The user sends a request.</strong> Say someone clicks "View Profile." The frontend fires off an HTTP request, something like <code>GET /api/profile</code>, carrying a method, a URL, maybe an authorization header, and sometimes a body. The browser genuinely has no idea your database even exists, it only ever talks to your backend API.</p>
<p><strong>The request reaches the Express server</strong>, which is just sitting there running somewhere, waiting.</p>
<pre><code class="language-tsx">import express from "express";

const app = express();

app.listen(3000, () =&gt; {
  console.log("Server running");
});
</code></pre>
<p>The moment that <code>GET /api/profile</code> request arrives, Express picks it up.</p>
<p><strong>The router figures out where it goes.</strong> A real app has dozens of endpoints, <code>/api/users</code>, <code>/api/products</code>, <code>/api/orders</code>, <code>/api/payments</code>, and the router's whole job is deciding which piece of code handles which one.</p>
<pre><code class="language-tsx">router.get("/profile", getProfile);
</code></pre>
<p>In plain terms, if someone hits <code>GET /profile</code>, run the <code>getProfile</code> function.</p>
<p><strong>Middleware runs before the controller ever sees the request.</strong> Think of it as a checkpoint the request has to pass through first.</p>
<pre><code class="language-plaintext">Request
   ↓
Authentication Middleware
   ↓
Validation Middleware
   ↓
Controller
</code></pre>
<p>Say a request comes in with <code>Authorization: Bearer token123</code>. The middleware's job is simply asking, is this token actually valid?</p>
<pre><code class="language-tsx">function authMiddleware(req, res, next) {
  const token = req.headers.authorization;

  if (!token) {
    return res.status(401).json({
      message: "Unauthorized"
    });
  }

  next();
}
</code></pre>
<p>If everything checks out, <code>next()</code> runs and the request keeps moving forward.</p>
<p><strong>The controller takes over.</strong></p>
<pre><code class="language-tsx">async function getProfile(req, res) {
  const userId = req.user.id;

  const profile = await userService.getProfile(userId);

  res.json(profile);
}
</code></pre>
<p>A controller's real job is just taking the request, pulling out what it needs, calling whatever service handles the actual work, and sending back a response. It really shouldn't be carrying heavy database logic itself.</p>
<p><strong>The service layer handles the actual business logic.</strong></p>
<pre><code class="language-tsx">async function getProfile(userId) {
  const user = await prisma.user.findUnique({
    where: { id: userId }
  });

  return user;
}
</code></pre>
<p>This is where the real database call happens.</p>
<p><strong>Prisma steps in here.</strong> When your code calls <code>prisma.user.findUnique()</code>, Prisma quietly builds the actual SQL behind it, something like <code>SELECT * FROM users WHERE id = 1;</code>, and PostgreSQL runs it.</p>
<pre><code class="language-plaintext">Service
   ↓
Prisma
   ↓
SQL Query
   ↓
PostgreSQL
   ↓
Result
</code></pre>
<p><strong>The database sends data back</strong>, something like <code>{ "id": 1, "name": "Rahul", "email": "rahul@test.com" }</code>, and it travels all the way back up through Prisma, the service, the controller, the Express response, and finally lands back at the frontend.</p>
<p>Put the whole thing together and you get this complete picture, from the click all the way to the UI updating.</p>
<pre><code class="language-plaintext">User Clicks Button
        |
Frontend Sends HTTP Request
        |
Express Server
        |
Router
        |
Middleware
        |
Controller
        |
Service
        |
Prisma / Drizzle / SQL
        |
PostgreSQL
        |
Response Returns
        |
Frontend Updates UI
</code></pre>
<p>A question that comes up a lot at this point is, why not just call the database straight from the controller? Technically, you absolutely can.</p>
<pre><code class="language-tsx">app.get("/users", async (req, res) =&gt; {
  const users = await prisma.user.findMany();
  res.json(users);
});
</code></pre>
<p>That'll work fine for a small project. The trouble shows up once the app grows, controllers stacked directly on the database without any layers in between tend to balloon in size, logic gets duplicated all over the place, testing becomes a pain, and maintaining any of it gets genuinely difficult over time. With proper layering, routes to controllers to services to a dedicated database layer, code stays organized, testing gets easier, teams can actually work in parallel without stepping on each other, and future changes stay contained instead of rippling everywhere.</p>
<h2>Where does all this code actually live</h2>
<p>Knowing the flow is one thing, but figuring out where this code should actually sit inside a real project is the next question worth answering.</p>
<p>Beginners typically start with something dead simple.</p>
<pre><code class="language-plaintext">src
├── index.ts
├── routes.ts
└── database.ts
</code></pre>
<p>That's genuinely fine for a small project. But as users pile up, products get added, payments show up, authentication gets bolted on, keeping everything crammed into a couple of files stops being manageable.</p>
<p>A more production style layout tends to look like this.</p>
<pre><code class="language-plaintext">src
├── index.ts
├── app.ts
├── config
│   └── env.ts
├── routes
│   ├── user.routes.ts
│   └── product.routes.ts
├── controllers
│   ├── user.controller.ts
│   └── product.controller.ts
├── services
│   ├── user.service.ts
│   └── product.service.ts
├── repositories
│   ├── user.repository.ts
│   └── product.repository.ts
├── db
│   └── prisma.ts
├── middleware
│   ├── auth.ts
│   └── error.ts
├── validators
└── utils
</code></pre>
<p>Every folder here is pulling its own weight. <code>index.ts</code> is really just the entry point, its only job is starting the server, nothing more.</p>
<pre><code class="language-tsx">import app from "./app";

app.listen(3000, () =&gt; {
  console.log("Server running");
});
</code></pre>
<p><code>app.ts</code> is where the Express app actually gets configured, middleware loaded, routes wired up.</p>
<pre><code class="language-tsx">import express from "express";
import userRoutes from "./routes/user.routes";

const app = express();

app.use(express.json());

app.use("/api/users", userRoutes);

export default app;
</code></pre>
<p>The routes layer's whole job is deciding which URL goes to which controller, nothing about the database belongs here.</p>
<pre><code class="language-tsx">router.get("/profile", getProfile);
</code></pre>
<p>The controller layer handles request and response, pulling data out and handing it off.</p>
<pre><code class="language-tsx">export async function getProfile(req, res) {
  const user = await userService.getProfile(req.user.id);
  res.json(user);
}
</code></pre>
<p>The service layer is where the actual business logic lives, deciding what needs to happen, in what order, and under what rules.</p>
<pre><code class="language-tsx">export async function getProfile(userId: number) {
  const user = await userRepository.findById(userId);
  return user;
}
</code></pre>
<p>The repository layer is what talks directly to the database.</p>
<pre><code class="language-tsx">export async function findById(id: number) {
  return prisma.user.findUnique({
    where: { id }
  });
}
</code></pre>
<p>Here's the real payoff of splitting things this way. If Prisma ever got swapped out for Drizzle down the line, only the repository layer would need to change, the service code sitting above it wouldn't need to know or care. That's really the whole point of the repository pattern.</p>
<p>And the database layer itself is usually just one shared client instance.</p>
<pre><code class="language-tsx">import { PrismaClient } from "@prisma/client";

export const prisma = new PrismaClient();
</code></pre>
<p>It's worth flagging why this matters, because beginners sometimes create a fresh Prisma client inside every single request.</p>
<pre><code class="language-tsx">app.get("/users", async (req, res) =&gt; {
  const prisma = new PrismaClient();
});
</code></pre>
<p>That's a genuinely bad habit, since it can spin up way too many database connections and hurt performance badly. Sticking to a single shared instance, sitting on top of a proper connection pool, is the better approach.</p>
<pre><code class="language-plaintext">Application
   ↓
Single Prisma Client
   ↓
Database Connection Pool
   ↓
PostgreSQL
</code></pre>
<p>A login flow is a good example of how all these layers cooperate. A <code>POST /login</code> request moves through the route, into the controller, into an auth service, which hits the database, checks the password, generates a JWT, and finally sends back a response.</p>
<pre><code class="language-tsx">const user = await userService.findByEmail(email);

const valid = await bcrypt.compare(password, user.password);

const token = jwt.sign({ id: user.id });
</code></pre>
<p>User input should never head straight to the database untouched either. Something like <code>{ "name": "", "email": "wrong-email" }</code> needs to pass through a validation layer first, usually something like Zod, Joi, or Yup.</p>
<pre><code class="language-tsx">const schema = z.object({
  email: z.string().email(),
});
</code></pre>
<p>Errors deserve real handling too, not just a stray <code>console.log(error)</code> buried in a try-catch. A cleaner approach routes errors through dedicated middleware instead.</p>
<pre><code class="language-tsx">app.use((error, req, res, next) =&gt; {
  res.status(500).json({
    message: "Something went wrong"
  });
});
</code></pre>
<p>Put together, the full architecture looks like this.</p>
<pre><code class="language-plaintext">Client
   ↓
Routes
   ↓
Controllers
   ↓
Services
   ↓
Repositories
   ↓
Prisma / Drizzle
   ↓
PostgreSQL
</code></pre>
<p>None of this is mandatory for every project though. A small app can genuinely get by with just routes, controllers, a db folder, and an entry file. Services and repositories are things you layer in as the project actually grows into needing them, not something you're required to bolt on from day one. The real goal here was never piling on more folders for their own sake, it's making sure every piece has a clear, single responsibility. If the database ever needs to change, the whole app shouldn't need rewriting. If auth logic changes, only auth related code should need touching. That's really what a maintainable backend comes down to.</p>
<h2>What's actually happening between Express, the ORM, and the database</h2>
<p>There's still one layer worth pulling apart properly, what's actually going on inside that database layer. When you write <code>await prisma.user.findMany()</code> or <code>await db.select().from(users)</code>, what actually reaches the database? Does Prisma talk to PostgreSQL directly? How does a connection even get established? And what happens when a hundred people hit your API at the same exact moment?</p>
<p>The flow isn't quite as direct as beginners often assume. It's not just Express straight to database, it's really more like this.</p>
<pre><code class="language-plaintext">Express Application
   ↓
Database Client / ORM
   ↓
Database Driver
   ↓
Database Server
</code></pre>
<p>Or more concretely.</p>
<pre><code class="language-plaintext">Express
   ↓
Prisma Client
   ↓
PostgreSQL Driver
   ↓
PostgreSQL
</code></pre>
<p>A database driver is really just the software layer handling the actual back and forth between your app and the database. Connecting Node.js to PostgreSQL usually starts with installing the <code>pg</code> package.</p>
<pre><code class="language-tsx">import { Pool } from "pg";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
});
</code></pre>
<p>That driver's job is opening connections, sending queries, and handing results back. Prisma doesn't reinvent PostgreSQL's wire protocol itself, it sits on top of a driver underneath it.</p>
<pre><code class="language-plaintext">Your Code
   ↓
Prisma
   ↓
Database Driver
   ↓
PostgreSQL
</code></pre>
<p>So <code>await prisma.user.findMany()</code> becomes <code>SELECT * FROM users;</code> under the hood, and the driver is what actually carries that query over to the database.</p>
<p>When your app first starts up, the database client gets initialized, though the actual connection usually isn't opened right away, it tends to get established only when it's actually needed, sometimes called a lazy connection.</p>
<pre><code class="language-plaintext">Server Start
   ↓
Database Client Initialize
   ↓
Connection Available
   ↓
Requests Accept
</code></pre>
<p>This is where connection pooling becomes a genuinely important concept. Picture a hundred users hitting your API at once. If every single request tried spinning up a brand new database connection from scratch, the database would get overwhelmed pretty fast. That's exactly what connection pools exist to prevent, a small set of ready-to-go connections sitting in a pool.</p>
<pre><code class="language-plaintext">Application
   ↓
Connection Pool
[Connection 1] [Connection 2] [Connection 3]
   ↓
PostgreSQL
</code></pre>
<p>A request comes in, grabs whatever connection's free, does its work, and hands that connection back to the pool once it's done. Say your pool caps out at 10 connections and 100 users show up at once, the first 10 requests grab connections right away, and everyone else just waits their turn until one frees up.</p>
<p>Development and production databases also tend to look pretty different day to day. Locally you're often just running something like PostgreSQL in a Docker container.</p>
<pre><code class="language-bash">docker run postgres
</code></pre>
<p>In production, you're usually looking at a managed PostgreSQL service instead, something like AWS RDS, Neon, Supabase's PostgreSQL offering, or Railway.</p>
<p>And database credentials should never be hardcoded directly into your code.</p>
<pre><code class="language-tsx">// don't do this
const db = "postgres://user:password@localhost";
</code></pre>
<p>That's both a security risk and a deployment headache waiting to happen. Environment variables are the better home for this.</p>
<pre><code class="language-plaintext">DATABASE_URL=postgresql://user:password@host/database
</code></pre>
<pre><code class="language-tsx">const url = process.env.DATABASE_URL;
</code></pre>
<p>Tracing a real request end to end makes all of this click. Say a <code>GET /api/users/10</code> request comes in. Express routes it through <code>router.get("/users/:id", getUser)</code>. The controller pulls the id out of the params and calls the service. The service calls the repository. The repository calls <code>prisma.user.findUnique({ where: { id } })</code>, which Prisma turns into <code>SELECT * FROM users WHERE id=10;</code>. PostgreSQL runs that, finds the row, and hands it back. From there it travels back up through Prisma, the repository, the service, the controller, and out through Express to the browser as something like <code>{ "id": 10, "name": "Rahul", "email": "rahul@test.com" }</code>.</p>
<p>Validation and authentication both slot in before the database ever gets touched. Registration data gets checked by something like Zod before it's allowed anywhere near the database. Protected routes run through JWT middleware first, and if that token's invalid, the database query simply never executes at all.</p>
<p>None of this is just academic trivia either. If you only ever memorize Prisma syntax or Express routing without understanding this flow, you can absolutely build things that work. But the moment something actually breaks, a slow query, a failing database connection, memory creeping up, a sluggish API response, that's exactly when understanding this architecture actually pays off. Backend development was never really about memorizing syntax. It's about understanding the journey a request takes from start to finish.</p>
<h2>Pulling it all together</h2>
<p>Putting the entire journey in one place looks like this.</p>
<pre><code class="language-plaintext">User Request
   ↓
Express Server
   ↓
Route
   ↓
Middleware
   ↓
Controller
   ↓
Service
   ↓
Repository
   ↓
ORM / Database Driver
   ↓
Database
   ↓
Response
</code></pre>
<p>Or laid out as a full production style diagram.</p>
<pre><code class="language-plaintext">                     Client
                       |
                       v
                HTTP Request
                       |
                       v
                Express Server
                       |
                       v
                   Router
                       |
                       v
                Middleware Layer
          (Auth, Validation, Logging)
                       |
                       v
                  Controller
                       |
                       v
                   Service
              (Business Logic)
                       |
                       v
                 Repository
              (Database Logic)
                       |
                       v
            Prisma / Drizzle / SQL
                       |
                       v
                PostgreSQL
</code></pre>
<p>A lot of beginners end up wondering whether the API itself is basically the database. It's not, an API is really just a communication layer. The frontend asks for something, <code>GET /api/products</code> say, and the backend decides whether that user's allowed to see it, what data actually needs fetching, what query the database needs, and how the response should be shaped. The API's really just acting as the messenger in between.</p>
<p>And there's a good reason the frontend never talks to the database directly, even though some databases technically allow it. Exposing credentials directly to the frontend is a straightforward security risk, since anyone poking around the client code could grab them. Business rules also need somewhere to live, checking stock availability, verifying payment completion, making sure a user isn't banned, and none of that belongs sitting in the frontend. And directly exposing a production database to the open internet is just asking for trouble. The safer shape is always frontend to backend API to database, never frontend straight to database.</p>
<h2>Mistakes that tend to show up along the way</h2>
<p>Cramming everything into the controller is a really common one early on, validating users, checking products, calculating prices, updating the database, sending emails, building the response, all crammed into one route handler. It works at first, but that controller balloons into a thousand line mess pretty quickly on a bigger project. Splitting things into controller, service, and repository keeps that from happening.</p>
<p>Scattering database queries across multiple controllers is another one, <code>prisma.user.findMany()</code> showing up in one controller file and again somewhere else entirely. That spreads database logic all over the codebase instead of keeping it contained inside a proper repository layer.</p>
<p>Treating the ORM like it's pure magic is a subtler trap. <code>prisma.user.findMany()</code> looks effortless on the surface, but underneath it's still just running <code>SELECT * FROM users;</code>. If the underlying SQL never actually makes sense to you, diagnosing real performance issues later becomes genuinely hard.</p>
<p>Ignoring proper error handling is another common gap, and in production, things will go wrong eventually, a database going down, bad input, network hiccups, unauthorized access attempts, all of it needs a proper path through central error handling middleware rather than getting quietly swallowed.</p>
<p>And hardcoding secrets directly into code, things like <code>const password = "123456"</code>, is a habit worth killing early. Environment variables exist specifically so things like <code>DATABASE_URL</code>, <code>JWT_SECRET</code>, and <code>API_KEY</code> never end up sitting in plain code.</p>
<p>Applications also change shape over time, a users table might start with just id, name, and email, then later pick up a phone number and a created_at timestamp. Migrations exist to track exactly these kinds of structural changes over time, tools like Prisma Migrate, Drizzle Kit, Flyway, or Liquibase are all essentially version control for your database's shape.</p>
<p>Good backends also tend to test layer by layer, controller tests checking whether the response looks right, service tests checking whether the business rules actually hold, repository tests checking whether the database queries themselves behave correctly. And logging matters a lot more once something's actually live, tools like Pino or Winston help capture things like request paths, status codes, and response times so debugging in production isn't a total guessing game. On top of that, keeping an eye on API response times, database performance, error rates, and general server health is just part of running something in production responsibly.</p>
<h2>What a solid backend developer actually needs to understand</h2>
<p>Realistically, it comes down to a handful of layers stacking on top of each other. HTTP basics, requests, responses, headers, status codes. Express itself, routes, middleware, controllers. The database, SQL, relationships, indexes, transactions. The ORM sitting on top of that, Prisma, Drizzle, migrations. The overall architecture tying it together, services, repositories, proper error handling. And finally, production concerns, deployment, logging, security.</p>
<p>A reasonable learning order tends to look like this: get comfortable with JavaScript or TypeScript fundamentals like async/await, promises, modules, and types. Then pick up HTTP basics, REST conventions, methods, status codes, headers. From there, build actual things with Express, simple APIs, middleware, basic auth. Then move into PostgreSQL properly, tables, relationships, joins, indexes. After that, bring in an ORM, Prisma first, then Drizzle. And finally, round it out with production concerns, Docker, deployment, logging, security.</p>
<p>Honestly, the easiest way to hold all of this in your head is to think of a backend like a restaurant. The frontend user is the customer. The API is the waiter. The business logic is the chef. The database is the kitchen. And the application's rules are basically the recipe everyone's following. A customer never walks straight into the kitchen themselves, there's a whole system standing between them and the food, and a backend works exactly the same way, frontend to API to business logic to database.</p>
<p>At the end of the day, the real skill in backend development was never memorizing one specific framework. Express, Prisma, Drizzle, PostgreSQL, these are all just tools. What actually matters is understanding how a request travels, how data gets stored, where business rules actually belong, how database queries get executed under the hood, and how to keep an architecture maintainable as it grows. Once those ideas are genuinely clear, picking up whatever new backend tool shows up next stops being intimidating, because at that point you're not just learning a tool, you're recognizing a system you already understand.</p>
]]></content:encoded></item><item><title><![CDATA[Prisma vs Drizzle vs Raw SQL: What Every Backend Developer Should Understand Before Choosing an ORM]]></title><description><![CDATA[When most developers start building backend applications, they're usually focused on one thing, getting an API working. Express routes, controllers, some auth, a bit of middleware, that's usually the ]]></description><link>https://surajsrggupta.hashnode.dev/prisma-vs-drizzle-vs-raw-sql-what-every-backend-developer-should-understand-before-choosing-an-orm</link><guid isPermaLink="true">https://surajsrggupta.hashnode.dev/prisma-vs-drizzle-vs-raw-sql-what-every-backend-developer-should-understand-before-choosing-an-orm</guid><category><![CDATA[database]]></category><category><![CDATA[prisma]]></category><category><![CDATA[drizzle]]></category><category><![CDATA[orm]]></category><category><![CDATA[SQL]]></category><dc:creator><![CDATA[Mr. S. Gupta]]></dc:creator><pubDate>Fri, 28 Aug 2026 11:07:22 GMT</pubDate><content:encoded><![CDATA[<p>When most developers start building backend applications, they're usually focused on one thing, getting an API working. Express routes, controllers, some auth, a bit of middleware, that's usually the whole mental model in the beginning.</p>
<p>But sooner or later, a bigger question shows up. How exactly should your application be talking to the database?</p>
<p>And that's when a bunch of unfamiliar words start floating around. Raw SQL, database drivers, ORMs, Prisma, Drizzle, query builders. Some people will tell you to never touch an ORM and just write SQL yourself. Others will swear Prisma made their life ten times easier. And then there's a smaller crowd that prefers Drizzle specifically because it stays close to SQL instead of hiding it.</p>
<p>So what's actually going on here? Why do all these tools even exist, and how do you know which one to reach for? Let's build this up from the ground.</p>
<h2>First, understand how data actually moves</h2>
<p>Before Prisma or Drizzle mean anything to you, it helps to see the full picture of how a request travels through a backend app.</p>
<p>Say a user opens your site and clicks something like "show my profile." That click travels roughly like this.</p>
<pre><code class="language-plaintext">Browser

   |
   v

Express API

   |
   v

Database Layer

   |
   v

PostgreSQL Database

   |
   v

Response back to the user
</code></pre>
<p>That "database layer" bit in the middle is where all the confusion tends to live. Your app needs some way to actually talk to PostgreSQL, and there are basically three common ways to do that: writing raw SQL yourself, using an ORM like Prisma, or using a query builder like Drizzle.</p>
<h2>Writing raw SQL directly</h2>
<p>This is the oldest, most direct route there is, you just write the SQL yourself.</p>
<p>Say you've got a users table.</p>
<pre><code class="language-plaintext">users

id
name
email
password
</code></pre>
<p>Finding a user is as simple as this.</p>
<pre><code class="language-sql">SELECT *
FROM users
WHERE id = 1;
</code></pre>
<p>The database speaks SQL natively, so there's no translation happening anywhere.</p>
<p>In Node.js, if you're on PostgreSQL, the <code>pg</code> package is the usual way to send queries like that directly.</p>
<pre><code class="language-tsx">import { Pool } from "pg";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
});

const result = await pool.query(
  "SELECT * FROM users WHERE id = $1",
  [1]
);

console.log(result.rows);
</code></pre>
<p>Your code sends SQL, PostgreSQL runs it, and the data comes straight back. Nothing hidden, nothing abstracted away.</p>
<p>Writing SQL yourself gives you complete control, obviously. You can write things like this without fighting any abstraction.</p>
<pre><code class="language-sql">SELECT
users.name,
COUNT(orders.id)
FROM users
JOIN orders
ON users.id = orders.user_id
GROUP BY users.name;
</code></pre>
<p>For gnarly reports like that, SQL is genuinely hard to beat. Writing it yourself also forces you to actually understand joins, indexes, execution plans, and performance in general, which is knowledge that pays off no matter what tool you end up using later. And there's no extra layer sitting between your code and the database either.</p>
<p>But raw SQL has real downsides once a project grows past a handful of queries. Imagine a codebase with hundreds of them scattered around.</p>
<pre><code class="language-tsx">const result = await pool.query(
`
SELECT *
FROM users
WHERE email=$1
`,
[email]
);
</code></pre>
<p>Now imagine someone renames a column somewhere down the line, say <code>email</code> becomes <code>email_address</code>. Every query touching that column is now quietly broken, and the database knows about the change immediately. Your TypeScript code has no idea until something crashes at runtime.</p>
<p>There's a sneakier version of this problem too.</p>
<pre><code class="language-tsx">const user = result.rows[0];

console.log(user.emial);
</code></pre>
<p>Spot the typo, <code>emial</code> instead of <code>email</code>. JavaScript won't say a word about it. You only find out once that line actually executes and blows up. This exact pain point is basically why ORMs exist in the first place.</p>
<h2>So what is an ORM, really</h2>
<p>ORM stands for Object Relational Mapping, which sounds fancier than it actually is. All it really means is that there's a bridge sitting between your programming language and your database.</p>
<p>Instead of writing this.</p>
<pre><code class="language-sql">SELECT *
FROM users
WHERE id=1;
</code></pre>
<p>You end up writing something closer to this.</p>
<pre><code class="language-tsx">user.findUnique({
 id: 1
})
</code></pre>
<p>The ORM takes what you wrote and turns it into actual SQL behind the scenes.</p>
<pre><code class="language-plaintext">Your TypeScript code

        |
        v

ORM

        |
        v

SQL query

        |
        v

Database
</code></pre>
<p>Developers reach for ORMs because modern apps need more than just sending queries around. They need type safety, proper migrations, schema management that doesn't require memorizing every table by hand, and honestly, just less repetitive boilerplate. That's the gap ORMs are trying to close.</p>
<p>In the TypeScript world today, the names that come up most are Prisma, Drizzle, TypeORM, and Sequelize. This piece focuses mainly on raw SQL, Prisma, and Drizzle, since those three are what most modern TypeScript backend projects are actually choosing between right now.</p>
<h2>Getting into Prisma</h2>
<p>Prisma is easily one of the most talked about ORMs in the TypeScript world, and it's usually one of the first names that comes up in any conversation about modern Node.js backends. So what problem is it actually solving?</p>
<p>At its core, Prisma lets your TypeScript app talk to a database using regular TypeScript code instead of SQL scattered everywhere. It supports PostgreSQL, MySQL, SQLite, SQL Server, and MongoDB.</p>
<pre><code class="language-plaintext">TypeScript code

        |
        v

Prisma Client

        |
        v

SQL queries

        |
        v

Database
</code></pre>
<p>You write TypeScript, Prisma turns it into the actual queries.</p>
<p>A typical Express plus Prisma plus PostgreSQL setup ends up looking roughly like this.</p>
<pre><code class="language-plaintext">Client

  |
  v

Express API

  |
  v

Service Layer

  |
  v

Prisma Client

  |
  v

PostgreSQL Database
</code></pre>
<p>Prisma Client is really the piece doing all the work here, it's the actual bridge between your backend code and the database sitting behind it.</p>
<p>Everything starts with a file called <code>schema.prisma</code>, which defines your entire database structure in one place.</p>
<pre><code class="language-plaintext">model User {
  id       Int    @id @default(autoincrement())
  name     String
  email    String @unique
  password String
}
</code></pre>
<p>That's basically saying, create a <code>User</code> table with these four columns, and Prisma understands exactly what that means.</p>
<p>Where raw SQL would need something like this to create the table.</p>
<pre><code class="language-sql">CREATE TABLE users (
 id SERIAL PRIMARY KEY,
 name TEXT,
 email TEXT UNIQUE,
 password TEXT
);
</code></pre>
<p>Prisma just needs the schema block shown above, and it handles turning that into actual database changes through something called a migration.</p>
<p>A migration is really just a recorded history of database changes over time. Say your User table starts out simple.</p>
<pre><code class="language-plaintext">User

id
name
email
</code></pre>
<p>Months later you decide to add a phone number field. Instead of manually altering the table yourself, you run something like this.</p>
<pre><code class="language-bash">npx prisma migrate dev --name add_phone_number
</code></pre>
<p>Prisma generates a migration file to track that change, something like a folder with <code>001_initial_setup</code> followed by <code>002_add_phone_number</code>, so your entire database history stays saved and versioned instead of living only in someone's memory.</p>
<p>Once your schema's defined, Prisma generates a client you actually use in your code.</p>
<pre><code class="language-tsx">const user = await prisma.user.findUnique({
  where:{
    id:1
  }
});
</code></pre>
<p>Behind the scenes, that quietly becomes this.</p>
<pre><code class="language-sql">SELECT *
FROM users
WHERE id=1;
</code></pre>
<p>You never had to type that SQL yourself.</p>
<p>The common operations map over pretty intuitively too. Creating a record that would normally be an <code>INSERT INTO users (name, email) VALUES (...)</code> becomes <code>prisma.user.create({ data: { name, email } })</code>. Fetching all users, which would be a plain <code>SELECT * FROM users</code>, becomes <code>prisma.user.findMany()</code>. Updating a record swaps <code>UPDATE users SET name='Aman' WHERE id=1</code> for <code>prisma.user.update({ where: { id: 1 }, data: { name: "Aman" } })</code>. And deleting swaps <code>DELETE FROM users WHERE id=1</code> for <code>prisma.user.delete({ where: { id: 1 } })</code>.</p>
<p>Where Prisma really earns its popularity is type safety. If your schema defines a <code>User</code> with <code>id</code>, <code>name</code>, and <code>email</code>, then writing something like this actually gives you working autocomplete on every field.</p>
<pre><code class="language-tsx">const user = await prisma.user.findMany();

user[0].email;
</code></pre>
<p>And if you accidentally typo it as <code>user[0].emali</code>, Prisma paired with TypeScript will flag that before your app even runs, which saves a genuinely surprising amount of debugging time down the line.</p>
<p>Real applications almost never have just one table either. Say a user has many posts.</p>
<pre><code class="language-plaintext">User

id
name

Post

id
title
userId
</code></pre>
<p>Prisma represents that relationship directly in the schema.</p>
<pre><code class="language-plaintext">model User {
 id Int @id @default(autoincrement())

 name String

 posts Post[]
}

model Post {

 id Int @id @default(autoincrement())

 title String

 userId Int

 user User @relation(
 fields:[userId],
 references:[id]
 )

}
</code></pre>
<p>And fetching a user along with their posts becomes a single readable call.</p>
<pre><code class="language-tsx">const user = await prisma.user.findUnique({
 where:{
  id:1
 },
 include:{
  posts:true
 }
});
</code></pre>
<p>Which gives you back something like a user object with a nested posts array, exactly the shape you'd want to work with in your app.</p>
<p>Prisma's biggest strengths really come down to how natural the developer experience feels, how strong the type safety is especially in TypeScript projects, how organized migrations become, how solid the documentation is, and how easily a whole team can look at the schema and immediately understand the database structure.</p>
<p>It's not without trade-offs though. Genuinely complex reporting queries can start feeling awkward compared to just writing raw SQL. Because Prisma hides SQL so effectively, developers who only ever learn Prisma can end up struggling later when they actually need to optimize something at the database level. And there's technically an extra layer sitting in the request flow, application to Prisma to SQL to database, instead of the more direct application to SQL to database path. For most apps that extra layer is a complete non issue, but it's worth knowing it's there.</p>
<p>Prisma tends to make the most sense when you're building APIs in TypeScript, want fast development, mostly need standard CRUD operations, and care a lot about developer experience. A lot of startups and SaaS products lean on it for exactly these reasons.</p>
<h2>Where Drizzle fits into the picture</h2>
<p>Drizzle is the other ORM that's picked up serious momentum in the TypeScript world, but understanding it really comes down to understanding one thing first, Prisma and Drizzle are built on genuinely different philosophies.</p>
<p>Prisma's whole approach is keeping developers away from database complexity as much as possible. Drizzle takes the opposite stance, keep developers close to SQL, just give them TypeScript's safety on top of it. Both are chasing the same end goal, a better development experience around your database, they just take very different roads to get there.</p>
<p>Drizzle is a lightweight TypeScript ORM that works with PostgreSQL, MySQL, and SQLite, and its whole focus is type safety, performance, staying close to SQL, and keeping the architecture lightweight.</p>
<pre><code class="language-plaintext">TypeScript code

        |
        v

Drizzle ORM

        |
        v

SQL query

        |
        v

Database
</code></pre>
<p>The philosophy difference is easiest to see side by side. Fetching all users in Prisma looks like this, and you never really need to think about what SQL it's generating underneath.</p>
<pre><code class="language-tsx">const user = await prisma.user.findMany();
</code></pre>
<p>Drizzle asks you to write something noticeably closer to actual SQL.</p>
<pre><code class="language-tsx">const users = await db
.select()
.from(usersTable);
</code></pre>
<p>You can basically read what's happening at the database level just by looking at the code.</p>
<p>Schemas work differently too. Where Prisma defines things in its own <code>schema.prisma</code> file, Drizzle defines the schema directly in TypeScript.</p>
<pre><code class="language-tsx">import {
 pgTable,
 serial,
 varchar
} from "drizzle-orm/pg-core";

export const users = pgTable("users", {

 id: serial("id").primaryKey(),

 name: varchar("name"),

 email: varchar("email")

});
</code></pre>
<p>So your database structure literally lives inside your TypeScript codebase, no separate schema language to learn.</p>
<p>The common operations feel like a TypeScript flavored version of SQL itself. Inserting a row that would be <code>INSERT INTO users (name, email) VALUES (...)</code> in SQL becomes <code>db.insert(users).values({ name, email })</code> in Drizzle. Fetching everything, which is <code>SELECT * FROM users</code>, becomes <code>db.select().from(users)</code>. And filtering, which would be <code>SELECT * FROM users WHERE id=1</code>, becomes <code>db.select().from(users).where(eq(users.id, 1))</code>.</p>
<p>People gravitate toward Drizzle for a handful of reasons. It's genuinely lightweight with very little unnecessary abstraction sitting in the way. Because it stays close to SQL, your actual SQL knowledge stays sharp and keeps growing rather than getting hidden behind an abstraction. The queries it generates tend to be predictable since there's less machinery translating your intent. And it was built with modern runtimes in mind too, things like edge environments and serverless setups.</p>
<p>That said, if you've never really learned SQL, Drizzle can feel a lot more confusing upfront than Prisma does, since Prisma is generally the easier on-ramp for total beginners. Using Drizzle well also genuinely requires understanding joins, relations, indexes, and how queries actually work, there's no hiding from that. And its ecosystem, documentation, and community, while solid, are still smaller than Prisma's more mature setup.</p>
<p>The simplest way to frame the difference is this. With Prisma you treat the database like a bunch of objects, <code>prisma.user.findMany()</code>, and the whole focus is developer experience. With Drizzle you treat the database more like SQL itself, <code>db.select().from(users)</code>, and the focus shifts toward performance and control.</p>
<h2>Looking at all three side by side</h2>
<p>At this point we've covered three real approaches, raw SQL, Prisma, and Drizzle, and architecturally they stack up like this.</p>
<pre><code class="language-plaintext">Raw SQL:      Application → SQL Query → Database
Prisma:       Application → Prisma Client → SQL → Database
Drizzle:      Application → Drizzle → SQL → Database
</code></pre>
<p>Take one simple operation, finding a user by email, given a basic users table with <code>id</code>, <code>name</code>, and <code>email</code>. Here's how each approach handles the exact same task.</p>
<p>Raw SQL:</p>
<pre><code class="language-tsx">const result = await pool.query(
`
SELECT *
FROM users
WHERE email=$1
`,
["rahul@test.com"]
);
</code></pre>
<p>Prisma:</p>
<pre><code class="language-tsx">const user =
await prisma.user.findUnique({

where:{
 email:"rahul@test.com"
}

});
</code></pre>
<p>Drizzle:</p>
<pre><code class="language-tsx">const user =
await db
.select()
.from(users)
.where(
 eq(
 users.email,
 "rahul@test.com"
 )
);
</code></pre>
<p>All three get you the exact same data back at the end of the day. What actually differs is how much control you want, how much abstraction you're comfortable with, and how much SQL you already know going in.</p>
<p>A mistake a lot of beginners make here is fixating on "which one's the fastest," when in real projects that's rarely the question that actually matters. The better questions are usually things like what the team's already comfortable with, how complex the project genuinely is, how strong everyone's database knowledge actually is, and how complicated the queries are likely to get. A simple SaaS app can usually get by just fine on Prisma alone. A database heavy application is where Drizzle, or even raw SQL in places, starts earning its keep.</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Raw SQL</th>
<th>Prisma</th>
<th>Drizzle</th>
</tr>
</thead>
<tbody><tr>
<td>Learning curve</td>
<td>Harder</td>
<td>Easier</td>
<td>Medium</td>
</tr>
<tr>
<td>SQL Control</td>
<td>Highest</td>
<td>Lowest</td>
<td>High</td>
</tr>
<tr>
<td>Type Safety</td>
<td>Low</td>
<td>Excellent</td>
<td>Excellent</td>
</tr>
<tr>
<td>Performance</td>
<td>Excellent</td>
<td>Good</td>
<td>Excellent</td>
</tr>
<tr>
<td>Developer Experience</td>
<td>Medium</td>
<td>Excellent</td>
<td>Good</td>
</tr>
<tr>
<td>Complex Queries</td>
<td>Excellent</td>
<td>Medium</td>
<td>Excellent</td>
</tr>
<tr>
<td>Beginner Friendly</td>
<td>Low</td>
<td>High</td>
<td>Medium</td>
</tr>
</tbody></table>
<h2>So what should you actually choose</h2>
<p>Should you always default to using an ORM? No, honestly. An ORM is a tool like any other, and no single tool is the right call for every project out there. Some apps genuinely move faster with one, others are better off without.</p>
<p>Raw SQL earns its place when you need maximum control over the database, when the app is genuinely database heavy, when queries get complex, or when performance really can't take a hit. Think analytics platforms, reporting systems, financial applications, or anything doing heavy data processing. Say you need a report answering something like "how much has each customer purchased over the last five years, and what's their average order value." That kind of query is just naturally easier to express directly in SQL.</p>
<pre><code class="language-sql">SELECT
customers.name,
COUNT(orders.id),
AVG(orders.amount)

FROM customers

JOIN orders

ON customers.id = orders.customer_id

GROUP BY customers.name;
</code></pre>
<p>There's really no substitute for SQL's raw power in cases like that. The catch is that leaning entirely on raw SQL across a large codebase gets unwieldy fast, hundreds of scattered query files, and you're left manually managing type safety yourself on top of it all.</p>
<p>Prisma tends to be the better call when you're building a TypeScript backend, doing mostly standard CRUD work, care about development speed, and have multiple developers working across the same codebase. Think SaaS products, admin dashboards, e-commerce APIs, or content management systems, basically anywhere operations like creating a user, updating a profile, creating an order, or pulling dashboard data are the daily bread and butter. Prisma genuinely boosts productivity here, turning something like a raw <code>SELECT * FROM users WHERE id=1</code> into a clean, readable <code>prisma.user.findUnique({ where: { id: 1 } })</code> that any teammate can understand at a glance.</p>
<p>Drizzle earns its spot when you're working in TypeScript, already know SQL reasonably well, care a lot about performance and control, and want something lightweight rather than heavy handed. It tends to appeal to developers who want the safety net of an ORM without feeling like they've been pulled too far away from the actual database.</p>
<p>In practice, plenty of real projects don't stick to just one approach. A startup might reasonably begin like this.</p>
<pre><code class="language-plaintext">Express → Prisma → PostgreSQL
</code></pre>
<p>Which is a genuinely practical starting point. As the app grows and analytics or complex reporting needs pile up, it's common to start layering raw SQL into specific parts of the app rather than ripping out Prisma entirely.</p>
<pre><code class="language-plaintext">Application
├── Prisma
└── Raw SQL Queries
        ↓
   PostgreSQL
</code></pre>
<p>Using one approach everywhere isn't some rule you're required to follow, and most real world codebases end up mixing them anyway. Startups often lean toward TypeScript plus Prisma plus PostgreSQL purely for development speed. Newer, more modern TypeScript projects increasingly reach for Drizzle instead, valuing that lightweight, SQL friendly approach. And larger scale systems often end up combining an ORM with raw SQL and dedicated database optimization work, simply because different parts of a large system run into genuinely different problems.</p>
<h2>Mistakes worth avoiding</h2>
<p>A few things trip people up consistently here. The first is treating the ORM itself as the database. Prisma isn't a database, Drizzle isn't a database, they're just tools for accessing one, whether that's PostgreSQL, MySQL, or MongoDB sitting underneath.</p>
<p>The second is skipping SQL entirely and jumping straight to Prisma. It feels productive early on, but it tends to backfire later when slow queries stop making sense, joins feel needlessly confusing, and database optimization becomes genuinely hard because there's no real foundation underneath the abstraction. An ORM was never meant to replace SQL, it's a layer sitting on top of it.</p>
<p>The third is chasing raw performance numbers above everything else. Developer productivity matters just as much in the real world. Shaving five milliseconds off a query while making development three months slower isn't automatically the smarter trade to make.</p>
<h2>A learning path worth following</h2>
<p>If starting from zero today, this is roughly the order worth going in. Start with SQL fundamentals, tables, primary keys, foreign keys, joins, indexes, transactions, all using something like PostgreSQL. Then spend some real time connecting directly with something like Node's <code>pg</code> package, so you actually understand how an application talks to a database without anything hidden in between. From there, move into Prisma, get comfortable with schemas, migrations, relations, and the type safety it brings. And finally, give Drizzle a proper try, so you understand what an SQL-first approach with a query builder actually feels like, along with the performance trade-offs that come with it.</p>
<h2>Where that leaves you</h2>
<p>The point of an ORM was never to replace SQL. It's there to make the developer experience better, nothing more grand than that.</p>
<p>A genuinely strong backend developer isn't someone who just happens to know Prisma or Drizzle well. It's someone who understands how a database actually works, how to write SQL when it's needed, what an ORM is actually simplifying for them, and just as importantly, when reaching for an ORM makes sense and when writing raw SQL directly is the smarter call.</p>
<p>Once that clarity sets in, choosing between these tools stops feeling like a guessing game. You start making the call based on what the project actually needs, not on whatever's trending that particular month.</p>
]]></content:encoded></item><item><title><![CDATA[SQL vs NoSQL, Explained the Way I Wish Someone Had Explained It to Me]]></title><description><![CDATA[When I was starting out with backend development, there was one question that kept messing with my head more than anything else.
Should I be learning SQL or NoSQL?
And every single tutorial seemed to ]]></description><link>https://surajsrggupta.hashnode.dev/sql-vs-nosql-explained-the-way-i-wish-someone-had-explained-it-to-me</link><guid isPermaLink="true">https://surajsrggupta.hashnode.dev/sql-vs-nosql-explained-the-way-i-wish-someone-had-explained-it-to-me</guid><category><![CDATA[SQL]]></category><category><![CDATA[NoSQL]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[supabase]]></category><category><![CDATA[database]]></category><dc:creator><![CDATA[Mr. S. Gupta]]></dc:creator><pubDate>Sun, 16 Aug 2026 09:39:17 GMT</pubDate><content:encoded><![CDATA[<p>When I was starting out with backend development, there was one question that kept messing with my head more than anything else.</p>
<p>Should I be learning SQL or NoSQL?</p>
<p>And every single tutorial seemed to have its own opinion on this. One guy swore PostgreSQL was the only sane option. Another one was pushing MongoDB purely because, in his words, "JSON is just easier." Then someone else came along and said forget databases entirely, use Firebase, you don't even need a real backend.</p>
<p>And just when I thought I had a decent grip on things, names like Supabase, Prisma, and Drizzle started showing up everywhere, and I genuinely couldn't tell anymore which of these were actual databases and which were just tools sitting on top of one.</p>
<p>If any of that sounds familiar, this is basically the article I wish existed back then.</p>
<p>By the time you're done reading this, you should have a clear picture of what a database actually is, what SQL and NoSQL even mean, why PostgreSQL, MySQL, Oracle, MongoDB, and Firestore aren't all the same thing despite sounding similar, and how to actually pick between SQL and NoSQL instead of just guessing.</p>
<p>Let's start from zero.</p>
<h2>What a database actually is</h2>
<p>Say you're building an online store. Your app needs somewhere to keep track of users, products, orders, payments, reviews, all of it.</p>
<p>Now, if you just shove all of that into regular JavaScript variables, the moment your server restarts, everything's gone. Poof.</p>
<p>A database exists to solve exactly that problem. It's just a system that holds onto your data permanently, so your app can read it back or update it whenever it needs to, restart or no restart.</p>
<p>Honestly, the simplest way to think about it is as a warehouse where your application's data actually lives.</p>
<h2>Everything mostly falls into two buckets</h2>
<p>Almost every app you'll build ends up leaning on one of two approaches, SQL or NoSQL.</p>
<p>And it's worth being clear here, these are categories, not products. Kind of like how Android and iOS are categories of operating systems, not the actual phone you're holding. SQL and NoSQL work the same way.</p>
<h2>What a SQL database actually looks like</h2>
<p>A SQL database keeps everything in tables. If you've ever messed around in Excel, this is going to feel oddly familiar.</p>
<p><strong>Users</strong></p>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Rahul</td>
<td>21</td>
</tr>
<tr>
<td>2</td>
<td>Aman</td>
<td>24</td>
</tr>
<tr>
<td>3</td>
<td>Priya</td>
<td>20</td>
</tr>
</tbody></table>
<p>Each row is one record, each column is one property, and the structure never really changes. If your table has <code>id</code>, <code>name</code>, and <code>age</code> as columns, then every single row follows that same shape, no exceptions.</p>
<p>That rigid consistency, honestly, is one of SQL's biggest selling points, even though it sounds boring on paper.</p>
<h2>Where SQL really shines, relationships</h2>
<p>Say your app also needs an Orders table.</p>
<p><strong>Orders</strong></p>
<table>
<thead>
<tr>
<th>Order ID</th>
<th>User ID</th>
<th>Product</th>
</tr>
</thead>
<tbody><tr>
<td>101</td>
<td>1</td>
<td>Laptop</td>
</tr>
<tr>
<td>102</td>
<td>2</td>
<td>Keyboard</td>
</tr>
</tbody></table>
<p>Here's the interesting part. Instead of writing the user's actual name into the Orders table over and over, it just stores the User ID. That ID is what links the two tables together.</p>
<p>That link is what people mean when they say relationship. SQL databases are built specifically to handle these kinds of connections well, which is exactly why banks, hospitals, schools, ERP systems, basically anything with serious business logic, tend to run on relational databases.</p>
<p>Some of the more common SQL databases you'll run into are PostgreSQL, MySQL, Oracle Database, Microsoft SQL Server, and SQLite. They're all built differently under the hood and each has its own quirks, but they all follow that same table based, relational way of organizing data.</p>
<h2>So what's NoSQL then</h2>
<p>The name trips a lot of people up. NoSQL doesn't actually mean "no SQL." It originally stood for "not only SQL," which honestly makes a lot more sense once you think about it.</p>
<p>Instead of forcing everything into tables, NoSQL databases give you other ways to organize your data. The one you'll bump into most often is the document database, and that's the category MongoDB and Firestore both fall under.</p>
<h2>Documents instead of rows and columns</h2>
<p>Rather than rows and columns, a document database stores, well, documents, and these usually look a lot like JSON.</p>
<pre><code class="language-json">{
  "name": "Rahul",
  "age": 21,
  "skills": ["Node.js", "React"],
  "address": {
    "city": "Patna",
    "country": "India"
  }
}
</code></pre>
<p>The thing is, every document is allowed to look different. One user might have an address field, another might not bother with one at all. One user might list ten skills, another just one. Nothing forces every document into the exact same shape.</p>
<p>That flexibility right there is basically the main reason a lot of developers reach for document databases in the first place.</p>
<h2>MongoDB</h2>
<p>MongoDB is probably the name most people think of first when document databases come up. Its structure goes database, then collections, then documents inside those collections.</p>
<pre><code class="language-plaintext">users

{
  "name": "Rahul",
  "age": 21
}

{
  "name": "Aman",
  "age": 24,
  "city": "Delhi"
}
</code></pre>
<p>Notice the second document has an extra city field that the first one doesn't. MongoDB is completely fine with that kind of mismatch, no complaints.</p>
<h2>Firestore</h2>
<p>Firestore, which lives under the Firebase umbrella, is also a document database. At a glance it looks a lot like MongoDB since both deal in JSON-ish documents, but there are real differences once you dig in.</p>
<p>Its structure goes database, then collections, then documents, and then something MongoDB doesn't really have, subcollections.</p>
<pre><code class="language-plaintext">users
   |
Rahul
   |
orders
   |
order1
order2
</code></pre>
<p>So a single user document can literally have another collection nested inside it. Firestore also comes with real time sync built right in, which is a big part of why it's such a common pick for chat apps and mobile apps that need things updating live.</p>
<h2>Wait, so are Firestore and MongoDB basically the same thing</h2>
<p>Nope, and this trips people up a lot. They're in the same category, sure, but they're different products entirely.</p>
<p>Think of it like PostgreSQL and Oracle. Both are SQL databases, both use tables, but nobody's out there claiming PostgreSQL and Oracle are interchangeable. It's the exact same logic with MongoDB and Firestore. Both document databases, but different architecture, different APIs, different pricing, different capabilities under the hood.</p>
<h2>Putting SQL and NoSQL side by side</h2>
<p><strong>SQL</strong> stores data in tables. Think PostgreSQL, MySQL, Oracle, SQL Server. It tends to be the go-to for banking, e-commerce, inventory systems, ERP software, and anything financial. Its strengths are solid relationships, real transactions, a structure that stays consistent, and genuinely powerful querying. Where it struggles a bit is when your data's shape keeps changing constantly.</p>
<p><strong>NoSQL</strong>, at least the document flavor, stores data as, well, documents. Think MongoDB and Firestore. It tends to work well for chat apps, social platforms, content management, mobile apps, and anything where the data shape shifts often. Its strengths are a flexible schema, easy handling of nested data, and generally faster initial development. Where it tends to struggle is once your relationships between data start getting genuinely complex.</p>
<h2>So which one should a beginner actually learn first</h2>
<p>If your end goal is backend development, learning SQL first is usually the smarter move, and here's the actual reason why. SQL forces you to actually understand things every developer eventually needs anyway, tables, primary keys, foreign keys, relationships, joins, transactions, indexes, all of it.</p>
<p>Once those concepts click, picking up MongoDB or Firestore later becomes almost easy. Going the other way around tends to be rougher, because document databases quietly hide a lot of the relational thinking you'd otherwise be forced to learn.</p>
<h2>A misconception worth clearing up</h2>
<p>A lot of beginners assume SQL is some outdated technology and NoSQL is the shiny modern replacement. That's just not accurate.</p>
<p>Companies like Stripe, GitHub, and Shopify, along with plenty of massive SaaS businesses, lean heavily on PostgreSQL even today. At the same time, plenty of successful products run just fine on MongoDB or Firestore. Neither one is objectively better across the board, they're just built to solve different kinds of problems, and the right pick really just comes down to what your application actually needs.</p>
<h2>Wrapping it up</h2>
<p>If I had to boil the whole thing down to one sentence each, it'd be this. SQL databases organize your data into related tables. NoSQL document databases organize your data as flexible, self contained documents.</p>
<p>Once that one distinction actually sinks in, a surprising number of other confusing topics start making sense on their own. Things like Supabase, Prisma, Drizzle, Firebase, and ORMs in general become a lot easier to reason about, because now you actually understand what's sitting underneath them.</p>
<p>Next up, I want to tackle another question that trips a lot of beginners up, is Supabase actually a database itself, or is it something else entirely wearing a database's clothes?</p>
]]></content:encoded></item></channel></rss>