<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: IshmamR</title>
    <description>The latest articles on DEV Community by IshmamR (@ishmam).</description>
    <link>https://dev.to/ishmam</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F547390%2F06894e20-a98b-4cf3-9c4d-accaf5b03785.jpg</url>
      <title>DEV Community: IshmamR</title>
      <link>https://dev.to/ishmam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ishmam"/>
    <language>en</language>
    <item>
      <title>I Was Fed Up with Mongoose's Types, So I Built My Own ODM</title>
      <dc:creator>IshmamR</dc:creator>
      <pubDate>Tue, 16 Jun 2026 13:24:08 +0000</pubDate>
      <link>https://dev.to/ishmam/i-was-fed-up-with-mongooses-types-so-i-built-my-own-odm-347i</link>
      <guid>https://dev.to/ishmam/i-was-fed-up-with-mongooses-types-so-i-built-my-own-odm-347i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Mongoose's type system was a constant source of friction. So I built &lt;a href="https://mongster.ishmam.dev" rel="noopener noreferrer"&gt;Mongster&lt;/a&gt;; a TypeScript ODM where one schema drives runtime validation, type inference, and index metadata. Typed filters, typed updates, typed aggregation, populate-via-&lt;code&gt;$lookup&lt;/code&gt;, transaction helpers, and index sync. One runtime dependency: the official &lt;code&gt;mongodb&lt;/code&gt; driver. &lt;code&gt;npm install mongodb mongster&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;It was a perfectly ordinary Wednesday when Mongoose finally broke me.&lt;/p&gt;

&lt;p&gt;I was writing a TypeScript query, something I'd done thousands of times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;populate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orgId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You already know the result type. &lt;code&gt;any&lt;/code&gt;. Of course it was &lt;code&gt;any&lt;/code&gt;. I forgot to do the usual dance of casting it, annotating it, and asserting it. And I'd been doing this dance for years.&lt;/p&gt;

&lt;p&gt;Mongoose do have "types". But they always felt like a thin layer of optimism painted over a fundamentally JavaScript-first API. &lt;code&gt;HydratedDocument&amp;lt;T, TMethodsAndOverrides, TVirtuals&amp;gt;&lt;/code&gt; is not a type you should be writing for dozens of models. &lt;code&gt;lean()&lt;/code&gt; returns something &lt;em&gt;close&lt;/em&gt; to your document type, but not quite, specially when &lt;code&gt;populate&lt;/code&gt;, or &lt;code&gt;select&lt;/code&gt; is involved. &lt;code&gt;InferSchemaType&lt;/code&gt; and the &lt;code&gt;Schema&lt;/code&gt; generic don't always agree (You need to do a lot more rituals for that). Discriminated unions, nested arrays, and refs? Good luck.&lt;/p&gt;

&lt;p&gt;So I did what SWE do when a tool consistently frustrates them: I started from scratch. :)&lt;/p&gt;

&lt;p&gt;The result was &lt;strong&gt;Mongster&lt;/strong&gt;; a type-safe MongoDB ODM for TypeScript. It's been my daily driver for a few months now, it's at v0.3.0 on npm, and I am writing this to tell why it exists and how it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  But Why Not Just Use…
&lt;/h2&gt;

&lt;p&gt;Fair question. There are a few other TypeScript-first options in this space. Here's why none of them were quite right for me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prisma is &lt;strong&gt;excellent&lt;/strong&gt;... for SQL
&lt;/h3&gt;

&lt;p&gt;It's a completely different development process, and not in a minor way. You define a &lt;code&gt;.prisma&lt;/code&gt; schema file in Prisma's own DSL, run a codegen step, and get a generated client that abstracts the database behind its own query API. For relational databases, that's a reasonable tradeoff. For MongoDB, it feels like fighting the database. MongoDB's document model, flexible schemas, nested arrays, arbitrary nesting, and native aggregation pipelines doesn't map cleanly to how Prisma thinks about data. You also lose the ability to express MongoDB-specific things naturally; if you want to use &lt;code&gt;$lookup&lt;/code&gt;, &lt;code&gt;$facet&lt;/code&gt;, or &lt;code&gt;$graphLookup&lt;/code&gt;, you're going outside the Prisma client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typegoose: adding fuel to the fire
&lt;/h3&gt;

&lt;p&gt;Typegoose solves the &lt;em&gt;syntax&lt;/em&gt; problem with Mongoose. You write classes with decorators instead of &lt;code&gt;new Schema({...})&lt;/code&gt; calls; but it doesn't solve the underlying problem because it's still Mongoose underneath. All the same type holes are still there; they're just expressed differently. And the API is heavily generic by design: &lt;code&gt;DocumentType&amp;lt;User&amp;gt;&lt;/code&gt;, &lt;code&gt;ReturnModelType&amp;lt;typeof User&amp;gt;&lt;/code&gt;, &lt;code&gt;Ref&amp;lt;User&amp;gt;&lt;/code&gt;, &lt;code&gt;ArraySubDocumentType&amp;lt;Address&amp;gt;&lt;/code&gt;. If you're writing a model with references and sub-documents, you're managing a lot of type parameters by hand. But that's what I have been doing with Mongoose for years anyway!&lt;/p&gt;

&lt;h3&gt;
  
  
  Papr: very close
&lt;/h3&gt;

&lt;p&gt;Papr is the one that's closest in spirit to what I wanted. It's a thin TypeScript wrapper over the official MongoDB driver with proper schema inference. No Mongoose, no codegen, good types. I have a lot of respect for the project. But couldn't use it in production.&lt;/p&gt;

&lt;p&gt;The problem is the API surface is intentionally minimal. You get &lt;code&gt;find&lt;/code&gt;, &lt;code&gt;findOne&lt;/code&gt;, &lt;code&gt;insertOne&lt;/code&gt;, &lt;code&gt;updateOne&lt;/code&gt;, &lt;code&gt;updateMany&lt;/code&gt;, &lt;code&gt;deleteOne&lt;/code&gt;, &lt;code&gt;deleteMany&lt;/code&gt;. The basics. There's no aggregation builder, no populate with type inference, no hooks, no transaction helpers, no index sync. That's a deliberate design choice and it's valid, but it wasn't enough for my use cases. Anything beyond basic CRUD meant dropping back to raw MongoDB driver calls, which defeats the purpose.&lt;/p&gt;

&lt;h3&gt;
  
  
  So I built Mongster
&lt;/h3&gt;

&lt;p&gt;Papr's philosophy (thin wrapper, native driver, real types) but with the full feature set I actually needed from Mongoose.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Main Bet
&lt;/h2&gt;

&lt;p&gt;I built Mongster one central idea: &lt;strong&gt;one schema should do everything&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That single schema declaration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should drive &lt;strong&gt;runtime validation&lt;/strong&gt;: Real errors with field paths and cause chains. No silent corruption.&lt;/li&gt;
&lt;li&gt;Should drive &lt;strong&gt;TypeScript inference&lt;/strong&gt;: Types flow from the schema, you never have to write them by hand!&lt;/li&gt;
&lt;li&gt;Should carry &lt;strong&gt;index metadata&lt;/strong&gt;: Indexes live with the fields they describe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else:&lt;/p&gt;

&lt;p&gt;The model API, filters, updates, projections, populate, and aggregation - all flows from that one schema. There is no need for manual generics. There is no &lt;code&gt;as any&lt;/code&gt; in normal use. When you write a bad filter, TypeScript tells you before MongoDB gets a chance to shrug.&lt;/p&gt;

&lt;p&gt;Under the hood, Mongster is a thin wrapper over the official &lt;code&gt;mongodb&lt;/code&gt; Node.js driver. Single runtime dependency - with no magic query layer, no vendored BSON. Just typed ergonomics on top of something that already works well.&lt;/p&gt;




&lt;h2&gt;
  
  
  I am not hating on Mongoose
&lt;/h2&gt;

&lt;p&gt;When I started building Mongster, I tried to keep the APIs as similar as possible to Mongoose. I tried with the exact API Mongoose schema builder has... but I failed.&lt;/p&gt;

&lt;p&gt;Mongoose was built before the TypeScript era. All the APIs are written beautifully for raw JavaScript. Making that schema API "type-safe" would make me go balder than I already am.&lt;/p&gt;

&lt;h2&gt;
  
  
  The eureka moment
&lt;/h2&gt;

&lt;p&gt;A couple weeks later while walking back to home, I realized Zod has a schema builder with types! I have been using it for a long time. I can just do a builder pattern!&lt;/p&gt;

&lt;p&gt;And so it started. My tug-of-war with TypeScript. I learned about the TypeScript types which I never had to on a day-to-day work. The &lt;a href="https://www.youtube.com/@MichiganTypeScript" rel="noopener noreferrer"&gt;Michigan Typescript&lt;/a&gt; channel was a big help. &lt;/p&gt;

&lt;p&gt;After 3 months of weekend-coding, I finally had a good enough ODM that worked.&lt;/p&gt;




&lt;h2&gt;
  
  
  A 30-Second Tour
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Defining a schema:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mongster&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+@&lt;/span&gt;&lt;span class="se"&gt;[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;member&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;withTimestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//  _id: ObjectId;&lt;/span&gt;
&lt;span class="c1"&gt;//  name: string;&lt;/span&gt;
&lt;span class="c1"&gt;//  email: string;&lt;/span&gt;
&lt;span class="c1"&gt;//  role: "admin" | "member" | "viewer";&lt;/span&gt;
&lt;span class="c1"&gt;//  createdAt: Date;&lt;/span&gt;
&lt;span class="c1"&gt;//  updatedAt: Date;&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inferInput&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Same; only _id, createdAt, and updatedAt are optional.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create a model and query:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mongster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mongster&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mongster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mongodb://localhost:27017/mongster&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// fully typed&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// TS error: 'roles' does not exist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Populate a reference:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;authorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectId&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// typed reference&lt;/span&gt;
  &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postSchema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// TypeScript knows authorId is now a User, not an ObjectId&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;populate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;authorId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;excludeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run a typed aggregation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$authorId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;totalPosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;totalPosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, composable, type-checked end to end.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Typing Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Two Type Spaces
&lt;/h3&gt;

&lt;p&gt;Every schema exposes two distinct TypeScript shapes, and keeping them separate is one of the design decisions I'm &lt;strong&gt;most satisfied&lt;/strong&gt; with.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;M.infer&amp;lt;typeof schema&amp;gt;&lt;/code&gt; is the &lt;strong&gt;storage type&lt;/strong&gt;: what a document looks like when you read it from MongoDB. All fields present, &lt;code&gt;ObjectId&lt;/code&gt;s are &lt;code&gt;ObjectId&lt;/code&gt;, timestamps are &lt;code&gt;Date&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;M.inferInput&amp;lt;typeof schema&amp;gt;&lt;/code&gt; is the &lt;strong&gt;input type&lt;/strong&gt;: what you pass to &lt;code&gt;create()&lt;/code&gt; or &lt;code&gt;insertOne()&lt;/code&gt;. Optional fields can be omitted, fields with &lt;code&gt;.default()&lt;/code&gt; don't need to be provided, &lt;code&gt;_id&lt;/code&gt; and &lt;code&gt;updatedAt&lt;/code&gt; are absent.&lt;/p&gt;

&lt;p&gt;Mongoose collapses these into one shape, which forces you to either over-specify (marking &lt;code&gt;_id&lt;/code&gt; as required when it isn't on insert) or under-specify (losing required-field guarantees). Mongster explicitly makes the distinction between &lt;em&gt;what goes in&lt;/em&gt; and &lt;em&gt;what comes out&lt;/em&gt;,&lt;/p&gt;

&lt;h3&gt;
  
  
  Filter and Update Inference
&lt;/h3&gt;

&lt;p&gt;The filter type knows your schema. This means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Valid&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;member&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ TS error: Type '"superadmin"' is not assignable to type 'RegExp | BSONRegExp | "admin" | "member"'.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;superadmin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ TS error: Type 'number' is not assignable to type 'undefined'.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$inc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update operators (&lt;code&gt;$set&lt;/code&gt;, &lt;code&gt;$inc&lt;/code&gt;, &lt;code&gt;$push&lt;/code&gt;, &lt;code&gt;$setOnInsert&lt;/code&gt;, and friends) are checked against the types of the fields they target. Projection is typed too — &lt;code&gt;find().include(["name", "social.github"])&lt;/code&gt; narrows the return type to only those fields, including nested dot-notation paths.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Interesting Internals
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Populate as an Aggregation Pipeline
&lt;/h3&gt;

&lt;p&gt;Most ODMs implement &lt;code&gt;populate&lt;/code&gt; as 2 separate queries: fetch the documents, extract the IDs, fire a second query, merge results.&lt;/p&gt;

&lt;p&gt;Mongster implements &lt;code&gt;populate&lt;/code&gt; as a &lt;code&gt;$lookup&lt;/code&gt; aggregation stage under the hood. This means it composes naturally with &lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;limit&lt;/code&gt;, and &lt;code&gt;project&lt;/code&gt;. You declare the ref once on the schema field with &lt;code&gt;.ref(() =&amp;gt; Model)&lt;/code&gt;. Mongster builds the right lookup stage from it at query time. There is no second round-trip.&lt;/p&gt;

&lt;h3&gt;
  
  
  Index Hashing and Sync
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;syncIndexes()&lt;/code&gt; sounds like a boring feature until you've spent an afternoon debugging index drift between environments.&lt;/p&gt;

&lt;p&gt;When you call it per model, or globally with &lt;code&gt;mongster.syncIndexes()&lt;/code&gt;, Mongster compares the indexes declared in your schema against the ones actually present in MongoDB. It normalizes both sides into a canonical form, hashes them, and diffs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unchanged&lt;/strong&gt; indexes are untouched&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing&lt;/strong&gt; indexes are created&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extra&lt;/strong&gt; indexes (present in DB but absent from schema) are dropped when &lt;code&gt;autoDrop&lt;/code&gt; is enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hashing and normalization logic is correct and reliable, but not glamorous or clever. I'll leave it up to you to check it out if you want to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transactions Without Ceremony
&lt;/h3&gt;

&lt;p&gt;MongoDB transactions require a &lt;code&gt;ClientSession&lt;/code&gt; threaded through every operation. In practice, this means adding an optional &lt;code&gt;session&lt;/code&gt; param everywhere and praying someone doesn't forget it.&lt;/p&gt;

&lt;p&gt;I took a different approach with Mongster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mongster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TxUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TxLog&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TxUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TxLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;role_change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ctx.use(Model)&lt;/code&gt; returns a transaction-scoped model that silently injects the session into every operation — no monkey-patching, no global state, no forgotten params. The transaction lifecycle is handled automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hooks With Predictable Order
&lt;/h3&gt;

&lt;p&gt;Hooks fire in a deterministic sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model.pre → schema.pre → query → schema.post → model.post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When both schema-level and model-level hooks exist for the same event, you always know what fires first. Supported lifecycle events cover &lt;code&gt;insert&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;bulkWrite&lt;/code&gt;, and their aliases (&lt;code&gt;save&lt;/code&gt;, &lt;code&gt;modify&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Missing
&lt;/h2&gt;

&lt;p&gt;This post won't be complete without these honest parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Populate only works on top-level scalar refs.&lt;/strong&gt; No &lt;code&gt;M.objectId().ref(...)&lt;/code&gt; inside arrays, and no nested-path refs &lt;strong&gt;&lt;em&gt;yet&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.ref()&lt;/code&gt; is terminal.&lt;/strong&gt; Chaining &lt;code&gt;.optional()&lt;/code&gt; or &lt;code&gt;.default()&lt;/code&gt; after it might break your queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregation expression operators aren't typed.&lt;/strong&gt; Stages like &lt;code&gt;$match&lt;/code&gt;, &lt;code&gt;$group&lt;/code&gt;, and &lt;code&gt;$sort&lt;/code&gt; are typed. Expression operators like &lt;code&gt;$toUpper&lt;/code&gt;, &lt;code&gt;$cond&lt;/code&gt;, and &lt;code&gt;$add&lt;/code&gt; are not. You have to use &lt;code&gt;.raw()&lt;/code&gt; for those pipelines with manual type-casting for now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transactions require a replica set.&lt;/strong&gt; This is a MongoDB constraint. But worth noting because I wasted a few hours debugging this.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No aggregation hooks yet.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;mongodb mongster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One runtime dependency. Everything else is inference.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm&lt;/strong&gt;: &lt;a href="https://www.npmjs.com/package/mongster" rel="noopener noreferrer"&gt;mongster&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/IshmamR/mongster" rel="noopener noreferrer"&gt;IshmamR/mongster&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;docs&lt;/strong&gt;: &lt;a href="https://mongster.ishmam.dev" rel="noopener noreferrer"&gt;mongster.ishmam.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have ever found yourself fighting Mongoose's type system more than writing your actual application code, I think the schema-first approach here is worth an afternoon. The types are the foundation everything else is built from in Mongster.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>mongodb</category>
      <category>node</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A simple component comparison between 3 popular #JavaScript frameworks</title>
      <dc:creator>IshmamR</dc:creator>
      <pubDate>Sat, 26 Dec 2020 10:47:11 +0000</pubDate>
      <link>https://dev.to/ishmam/a-simple-component-comparison-between-3-popular-javascript-frameworks-12h0</link>
      <guid>https://dev.to/ishmam/a-simple-component-comparison-between-3-popular-javascript-frameworks-12h0</guid>
      <description>&lt;h2&gt;
  
  
  Vue.js :
&lt;/h2&gt;

&lt;p&gt;Vue.js is an open-source model–view–viewmodel front end JavaScript framework for building user interfaces and single-page applications. It was created by Evan You, and is maintained by him and the rest of the active core team members.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F53u9klh71ppklg40pi0p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F53u9klh71ppklg40pi0p.png" alt="vue" width="800" height="905"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  React :
&lt;/h2&gt;

&lt;p&gt;React is an open-source, front end, JavaScript library for building user interfaces or UI components. It is maintained by Facebook and a community of individual developers and companies. React can be used as a base in the development of single-page or mobile applications.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjmyb89s8vjf4alhl2cqj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjmyb89s8vjf4alhl2cqj.png" alt="react" width="800" height="740"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular :
&lt;/h2&gt;

&lt;p&gt;Angular is a TypeScript-based open-source web application framework led by the Angular Team at Google and by a community of individuals and corporations. Angular is a complete rewrite from the same team that built AngularJS.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgqmyvdid35zht76cel5k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgqmyvdid35zht76cel5k.png" alt="angular" width="800" height="870"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>vue</category>
      <category>angular</category>
    </item>
  </channel>
</rss>
