Building a Validation Library: Lessons from valiend
What I learned shipping my first production npm package
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.
- API design is product design. Developer ergonomics matter as much as runtime performance.
- Documentation is half the product. A library nobody understands is a library nobody uses.
- Semver matters. One accidental breaking change in a patch release taught me to be rigorous about versioning.
- 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.