Download Resume
Back to Journal
Backend Engineering4 May 2026 9 min read

Building Real Validation and Type Safety into mongoosql-core

I didn’t want another ORM that just “looks like Mongoose.” I wanted one that enforces correctness — at runtime and at compile time. Today was about closing that gap.

ORMTypeScriptValidationBackendArchitecture

Today was one of those days where the gap between “it works” and “it’s actually solid” became very clear.

mongoosql-core already worked.

You could define schemas.

You could query across MongoDB, Postgres, and MySQL.

You could even use a Mongoose-style API across all three.

But there was a problem:

It wasn’t strict enough.

And in backend systems, that eventually becomes a liability.

What Was Missing

The schema system was too permissive.

You could define fields, sure — but:

* Validation wasn’t comprehensive

* TypeScript didn’t enforce correctness strongly enough

* Some invalid states could slip through silently

That’s fine for a prototype.

Not fine for something people will rely on.

Step 1: Build Real Validators (Not Just Basic Checks)

I extended the schema definition to support a full validation suite — similar to what developers expect from Mongoose, but engine-agnostic.

Instead of just:

* required

* enum

I introduced:

* min / max (numbers)

* minLength / maxLength / exact length (strings)

* match (regex)

* minItems / maxItems (arrays)

* custom validators

* normalization (trim, lowercase, uppercase)

What this unlocks

Now you can define constraints like:

* “email must match a pattern AND be lowercase”

* “code must be exactly 6 characters”

* “array must contain at least 1 item but no more than 10”

And it’s enforced before anything hits the database.

That matters.

Because database constraints alone don’t give good developer feedback — they just fail late.

Step 2: Move Validation to the Right Place

Validation now runs inside the model layer before:

* insertOne

* save()

* updateOne

* updateMany

So the flow becomes:

input → validate → normalize → persist

Not:

input → persist → database error

That shift alone makes debugging significantly easier.

Step 3: Add Normalization (Subtle but Important)

This part is easy to underestimate.

But things like:

* trimming strings

* forcing lowercase emails

* enforcing uppercase enums

…remove entire classes of bugs.

No more:

“Why do we have two users with the same email but different casing?”

Now it’s normalized before storage.

Step 4: Fix Type Safety Properly

This was the bigger structural change.

I made the entire model system generic:

* Schema

* Model

* Document

* Query

And wired them together.

So now:

* queries return correctly typed results

* setters reject invalid types at compile time

* filters are type-safe

Example impact

Trying to do this:

set('age', 'oops')

…fails at compile time.

That’s the kind of safety you want — before runtime, before production.

Step 5: Align Runtime and Compile-Time Guarantees

This was the real goal.

Not just validation.

Not just TypeScript.

Both.

Because:

* TypeScript alone can be bypassed

* Runtime validation alone is too late

But together, they reinforce each other.

What Changed Conceptually

Before:

The schema described structure, loosely.

Now:

The schema enforces correctness — strictly.

That’s a different level of responsibility.

Why This Matters

Most ORMs make a tradeoff:

* either they are flexible but unsafe

* or strict but locked into one database

mongoosql-core is trying to avoid that tradeoff.

Same API.

Multiple databases.

Strong guarantees.

The Subtle Realization

This wasn’t about adding features.

It was about removing ambiguity.

Every unclear edge case in a system eventually becomes a bug.

Today was about eliminating those edges.

What’s Next

The next step is pushing this further:

* stricter schema inference (generate types FROM schema, not just pass them in)

* deeper nested object validation

* better migration automation

Because right now:

The foundation is finally solid enough to build on.

What I Took From This

* “Works” is not the same as “safe”

* Validation is part of your architecture, not just input checking

* Type safety should guide developers, not just exist

* Small inconsistencies (like casing or length) compound over time

Why This One Matters

This wasn’t visible work.

No UI.

No flashy feature.

But it fundamentally changes how reliable the system is.

And if this library is going to be used in real systems, that reliability is everything.

EE

Emmanuel Eko

SDET & QA Architect

More posts