All posts

Building a Validation Library: Lessons from valiend

What I learned shipping my first production npm package

Building a Validation Library: Lessons from valiend article cover

Why I built valiend

Validation is one of those problems every developer solves multiple times. After writing the same isEmail and isRequired helpers across three different projects, I decided to extract them into a standalone package.

What started as a small utility grew into something I'm genuinely proud of. Here's what I learned along the way.

Designing the API first

Before writing a single line of implementation, I wrote down how I wanted to use the library:

import { validate } from 'valiend';

const result = validate(userInput, {
  email: ['required', 'email'],
  age: ['required', 'min:18', 'max:120'],
  username: ['required', 'min:3', 'max:20', 'alphanumeric'],
});

if (!result.valid) {
  console.log(result.errors); // { email: ['Must be a valid email'] }
}

Writing usage examples before implementation is one of the best API design techniques I know. It forces you to think from the consumer's perspective.

The hard parts

Tree-shaking

One of the first feedback items I got was that the bundle was bloated. Users only needed two or three validators but were pulling in the whole library.

The fix was straightforward — export validators individually so bundlers can tree-shake:

// Before
import { validate } from 'valiend';

// After — only import what you need
import { required, email, minLength } from 'valiend/validators';

Error message customization

Hard-coded error messages are a trap. Different projects, different languages, different tones. I added a message factory pattern:

validate(data, rules, {
  messages: {
    required: (field) => `${field} cannot be empty`,
    email: () => 'Please enter a valid email address',
  }
});

Type safety

Adding TypeScript types late in the process was painful. The lesson: write types from the start, even if you're writing JavaScript. JSDoc works fine if you're not ready to commit to TypeScript.

Takeaways

Shipping something imperfect is infinitely more valuable than never shipping something perfect.

  1. API design is product design. Developer ergonomics matter as much as runtime performance.
  2. Documentation is half the product. A library nobody understands is a library nobody uses.
  3. Semver matters. One accidental breaking change in a patch release taught me to be rigorous about versioning.
  4. Tests are your contract. A comprehensive test suite lets you refactor confidently.

You can check out valiend at valiend.com or explore the source on GitHub.