Sequelize Express validation and error handling

Sequelize + Express Validation & Error Handling: Build Production-Ready Node.js APIs

You built your CRUD endpoints.
Your requests return 200.
Your database saves data.

Everything works.

But here’s the real question:

👉 Is your API ready for production?

In this post, we’ll take our Sequelize + Express project to the next level by adding:

  • ✅ Proper model validation
  • ✅ Centralized error handling
  • ✅ Clean, meaningful HTTP responses
  • ✅ Resilience against real-world failures

If you’re building APIs with Node.js, this is a must-have upgrade.

🚀 Complete JavaScript Guide (Beginner + Advanced)

🚀 NodeJS – The Complete Guide (MVC, REST APIs, GraphQL, Deno)


The Problem: Trusting the API Consumer

Earlier, we implemented full CRUD for a User model:

  • Create user
  • Get user(s)
  • Update user
  • Delete user

It worked perfectly.

But there was one big issue:

We trusted the client to send valid name and email.

In real life, that assumption will break your system.

Users send:

  • Empty strings
  • Invalid emails
  • Missing fields
  • Unexpected values

Your API must defend itself.


Step 1: Model-Level Validation in Sequelize

Validation starts when designing your model.

Let’s look at our User model.


Validating the name Field

What do we want?

  • The name must exist
  • It must not be empty
  • It shouldn’t be extremely long
  • It must respect the database column limit

Since Sequelize created a VARCHAR(255) field in MySQL, we should enforce a stricter limit at the application level.

Example:

@AllowNull(false)
@Column({
  validate: {
    len: [1, 100]
  }
})
name!: string;

What this does:

  • @AllowNull(false) → Prevents null values
  • len: [1, 100] → Enforces minimum and maximum length

Now invalid names never reach the database.


Validating the email Field

For email, we want:

  • Required
  • Unique
  • Proper email format

Example:

@AllowNull(false)
@IsEmail
@Column({
  unique: true
})
email!: string;

Now Sequelize automatically:

  • Rejects missing emails
  • Rejects invalid formats
  • Prevents duplicate emails

Model validation is your first line of defense.

But we’re only halfway done.


Step 2: Centralized Error Handling in Express

Validation is great.

But what happens when it fails?

Many developers use try/catch inside every route. That works — but it doesn’t scale.

If your app has:

  • Multiple models
  • Multiple services
  • Multiple validations

You’ll end up duplicating error logic everywhere.

There’s a better way.


Use Express Error Middleware

Express has built-in support for centralized error handling.

Instead of catching errors in every route, let them bubble up.

Then handle them in one place.


Creating a Custom Error Handler

Create:

src/middleware/errorHandler.ts

A typical error handler looks like this:

export default function errorHandler(
  error: unknown,
  req: Request,
  res: Response,
  next: NextFunction
) {
  if (res.headersSent) {
    return next(error);
  }  // custom logic here
}

Important:

We first check res.headersSent.
If headers were already sent, we pass the error to Express’ default handler.

This prevents double responses.


Step 3: Handling Sequelize Validation Errors

Sequelize throws a ValidationError when model validation fails.

We can detect it like this:

if (error instanceof ValidationError) {
  return res.status(422).json({
    error: "Validation error",
    details: error.errors
  });
}

Why 422?

422 Unprocessable Entity means:

The request was syntactically correct, but semantically invalid.

Perfect for validation failures.


Step 4: Safe Error Message Extraction

In JavaScript, anything can be thrown.

In TypeScript, errors are typed as unknown.

So we create a utility:

export function getErrorMessage(error: unknown): string {
  if (error instanceof Error) {
    return error.message;
  }  if (typeof error === "string") {
    return error;
  }  return "An error occurred";
}

Now we can safely return error messages without crashing the app.


Step 5: Handling Database Connection Errors

When the database is unavailable, Sequelize throws a ConnectionError.

Instead of exposing internal details, we return:

if (error instanceof ConnectionError) {
  return res.status(503).json({
    error: "Database unavailable"
  });
}

Why 503?

503 Service Unavailable means:

The server is temporarily unable to handle the request.

This is perfect when your DB is down.


Step 6: Handling Database-Level Errors

Sequelize also throws DatabaseError for:

  • Invalid columns
  • SQL syntax errors
  • Constraint violations

Example:

if (error instanceof DatabaseError) {
  return res.status(500).json({
    error: getErrorMessage(error)
  });
}

These are server-side problems.


4xx vs 5xx: Know the Difference

Understanding status codes is critical.

4xx → Client Mistake

Examples:

  • 400 Bad Request
  • 401 Unauthorized
  • 404 Not Found
  • 422 Validation Error

The client sent something wrong.


5xx → Server Problem

Examples:

  • 500 Internal Server Error
  • 503 Service Unavailable

Something failed on your side.

Your API consumers depend on these signals.


Step 7: Register the Error Middleware

This is crucial.

In server.ts, the error handler must be the last middleware:

app.use(errorHandler);

Always register it after all routes.


Testing the Improvements

Try these scenarios:

  • Create user without name
  • Create user with empty name
  • Use invalid email format
  • Stop the database
  • Request invalid column

Now instead of crashes or messy stack traces, you get:

  • Structured JSON errors
  • Proper HTTP status codes
  • Clean, professional responses

What We Achieved

After implementing validation + centralized error handling:

✅ Bad data is rejected early
✅ Errors are meaningful and structured
✅ Sensitive stack traces are hidden
✅ The API behaves predictably
✅ Database failures are handled gracefully

This is how you move from:

“It works on my machine”

to

“It’s production-ready.”


What’s Next?

There’s still one missing piece.

How do you keep your database structure consistent across:

  • Local development
  • Staging
  • Production
  • Multiple team members

The answer:

👉 Sequelize Migrations

Share this article

Similar Posts