npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@anclatechs/serde-js

v1.0.7

Published

Composable, schema-driven serialization and validation for JavaScript.

Readme

@anclatechs/serde-js

A context-aware, declarative serialization and validation framework for JavaScript.

serde-js lets you define one schema that can:

  • validate user input (writes)
  • safely serialize output (reads)
  • enforce conditional logic via context
  • support nested objects and arrays
  • aggregate errors with precise paths

Inspired by Django REST Framework, Zod, and Joi — but designed to stay lightweight, explicit, and extensible.


Table of Contents


Installation

npm install @anclatechs/serde-js
const {
  Serializer,
  CharField,
  NumberField,
  IntegerField,
  BooleanField,
  DateTimeField,
  DateField,
  EmailField,
  UrlField,
  ArrayField,
  ObjectField,
  JsonField,
} = require("@anclatechs/serde-js");

Core Concepts

1. One schema — many use cases

A single Serializer definition can:

  • validate input (e.g. signup, update)
  • serialize output (API responses)
  • behave differently based on context

No duplicate schemas. No conditionals scattered across controllers.


2. Fields are intelligent

Each field:

  • knows whether it is required
  • can be optional or have defaults
  • supports custom validators
  • can be read-only or write-only
  • can be conditionally included

3. Context drives behavior

Context is an arbitrary object you pass at runtime. Example:

{
  mode: "input" | "output",
  isSignup: true,
  userRole: "admin"
}

Fields and validators can react to it.


Quick Start

const UserSerializer = new Serializer({
  name: new CharField(),
  age: new IntegerField().optional(),
  email: new EmailField(),
});

const result = UserSerializer.serialize({
  name: "Ada",
  age: 30,
  email: "[email protected]",
});

console.log(result.data); // serialized output
console.log(result.errors); // error mapping
console.log(result.isValid()) // boolean status for any available error
console.log(result.verboseErrorList()) // simplified, "verbosed" errors data retunred as an Array of object; accessible via .message.

Fields

Base Field

All fields inherit from Field.

Supported features:

new Field()
  .optional()
  .default(value)
  .validate(fn)
  .onlyIf((ctx) => boolean)
  .readOnly()
  .writeOnly();

CharField, in addition to the validate function, also provide helper methods:

.enumOptions([])
.minLength(num)
.maxLength(num)

NumberField and IntegerField also both support the .min() and .max() helper methods.


Available Field Types

| Field | Description | | --------------- | -------------- | | CharField | String values | | NumberField | Any number | | IntegerField | Integer-only | | BooleanField | Boolean | | DateTimeField | JS Date (ISO) | | DateField | Date-only | | EmailField | Email string | | UrlField | URL string | | ArrayField | Arrays | | ObjectField | Nested structured objects | | JsonField | dynamic JSON object |


Serializer

Creating a Serializer

const UserSerializer = new Serializer(schema, options);

Options

{
  many?: boolean // expect an array of objects
}

Read vs Write Modes

Why this matters

Often:

  • some fields should only be accepted (password)
  • some should only be returned (id, timestamps)

Example

const UserSerializer = new Serializer({
  id: new IntegerField().readOnly(),
  email: new EmailField(),
  password: new CharField().writeOnly(),
});

Input (write)

UserSerializer.serialize(req.body, { mode: "input" });

Output (read)

UserSerializer.serialize(user, { mode: "output" });

Context-Aware Rules

onlyIf(fn)

Conditionally include a field.

role: new CharField().onlyIf((ctx) => ctx.userRole === "admin");

Signup-only fields

password: new CharField().onlyIf((ctx) => ctx.isSignup);

If the condition fails, the field is:

  • not required
  • not validated
  • not serialized

Default Values

The Field.default() method allows you to provide a value when the input does not specify one.

It can be either:

1. A constant value

const UserSerializer = new Serializer({
  name: new CharField().default("Ada"),
});

2. A function for dynamic computation

You can compute the default value based on other fields in the serializer or context:

const UserSerializer = new Serializer({
  name: new CharField(),
  age: new IntegerField(),
  ageInTwoYears: new IntegerField().default((_, root, ctx) => root.age * 2).readOnly(),
});

Parameters of the default function

| Parameter | Description | | --------- | ---------------------------------------------------------------------------------------------------------------------- | | value | The input value for this field. If the input is missing, it will be undefined. May be ignored and represented as _. | | root | The partially built serialized object. Lets you reference other fields in the same serializer. | | ctx | Optional context object passed to serialize(input, context). Useful for user info, request metadata, etc. |

⚠️ Order matters: The field you reference in root (e.g., age) must appear before the dependent field (ageInTwoYears) in the schema.


Validation

Custom Validators

age: new IntegerField().validate((v) => v >= 18 || "Must be 18+");

Validators return:

  • true → pass
  • false or string → fail

Reusable Validator Helpers

const min = (n) => (v) => v >= n || `Must be ≥ ${n}`;
const minLength = (n) => (v) => v.length >= n || `Min length ${n}`;
password: new CharField().validate(minLength(8));

Nested Serializers

ObjectField

const AddressSerializer = new Serializer({
  street: new CharField(),
  city: new CharField(),
});

const UserSerializer = new Serializer({
  name: new CharField(),
  address: new ObjectField(AddressSerializer),
});

Nested errors are merged automatically:

address.city: Field is required

ArrayField

Array of scalars

tags: new ArrayField(new CharField());

Array of objects

posts: new ArrayField(PostSerializer);

Each item is validated independently with full error paths:

posts[2].title: Field is required

Error Handling

Errors are aggregated on the serializer:

serializer.errors;

Example:

{
  "email": "Invalid EmailField",
  "address.city": "Field is required",
  "tags[1]": "Invalid CharField"
}

No exceptions. Full visibility.


Schema Introspection

describe()

UserSerializer.describe();

Returns:

{
  email: {
    type: "EmailField",
    required: true
  },
  tags: {
    type: "ArrayField",
    child: "CharField"
  }
}

Useful for:

  • documentation
  • form generation
  • schema inspection

Advanced Patterns

Cross-field validation

password: new CharField(),
confirmPassword: new CharField().validate((v, ctx) =>
  v === ctx.password || "Passwords do not match"
)

Role-based schemas

salary: new NumberField().onlyIf((ctx) => ctx.userRole === "admin");

Partial updates

new CharField().optional().onlyIf((ctx) => ctx.isUpdate);

Design Philosophy

  • Explicit over magical
  • One schema, many flows
  • Context drives behavior
  • Errors should be precise
  • Composition over inheritance

serde-js is intentionally unopinionated about:

  • HTTP frameworks
  • Databases
  • ORMs

It fits cleanly into Express, Fastify, NestJS, or serverless setups.


Final Note

This library is intentionally small but powerful; supporting optional fields, default values, custom validators, aggregated error reporting, and context-aware serialization. Fields are required by default, your may define defaults or validation rules, and can dynamically include or validate data based on your preferred runtime context. Validation errors are collected and returned in a structured, predictable format suitable for APIs and batch processing.

If you understand how context flows, you can model:

  • signup
  • updates
  • admin overrides
  • read/write separation

…without ever duplicating schemas.

📜 License

MIT — use it, hack it, ship it.

Happy building 🚀