Benchmarks
How fast and how small this library is, next to its previous major version and the other libraries on npm that validate Spanish IDs. Every number on this page is read from bench/results/latest.json when the site is built: nothing is typed by hand. If a library is faster or smaller at something, the tables show it.
| Machine | AMD EPYC 9V74 80-Core Processor |
|---|---|
| Cores | 4 |
| Memory | 16 GiB |
| Operating system | linux 6.17.0-1022-azure (x64) |
| Node.js | v24.21.0 |
| Date | 2 October 2026 |
| Commit measured | 1166241 |
| Method | 3 rounds; each task 2,000 ms after 500 ms of warmup, in its own process; the figure is the median round |
Absolute numbers depend on the machine. The ratios between libraries of one run carry over between machines far better, so compare those.
Throughput
Section titled “Throughput”Millions of validations per second (M ops/s, higher is faster), each library called the way its documentation shows, with its default options, on the same valid and invalid documents in canonical form. “This build is” gives how many times as fast as each library this build is, in the same run. unsupported means the library has no validator for that type.
| Library | M ops/s | This build is |
|---|---|---|
| nif-dni-nie-cif-validation this build | — | |
| @maistik/validate-nif 2.0.1 | 6.4× as fast | |
| dni-js 1.0.0 | 6.8× as fast | |
| nif-dni-nie-cif-validation 1.0.11 | 7.0× as fast | |
| better-dni 4.4.2 | 7.1× as fast | |
| spain-id 1.1.14 | 8.6× as fast | |
| validator.js isIdentityCard(x, "ES") 13.15.35 | 10.5× as fast | |
| validator.js isTaxID(x, "es-ES") 13.15.35 | 14.3× as fast | |
| @kreyo/nif-validator 0.1.0 | 21.6× as fast | |
| jsvat 2.5.4 | 28.6× as fast | |
| stdnum 1.12.6 | 146.3× as fast |
No other library was faster on the DNI set.
| Library | M ops/s | This build is |
|---|---|---|
| nif-dni-nie-cif-validation this build | — | |
| @maistik/validate-nif 2.0.1 | 6.9× as fast | |
| better-dni 4.4.2 | 7.1× as fast | |
| nif-dni-nie-cif-validation 1.0.11 | 9.0× as fast | |
| dni-js 1.0.0 | 12.8× as fast | |
| spain-id 1.1.14 | 14.4× as fast | |
| validator.js isTaxID(x, "es-ES") 13.15.35 | 16.8× as fast | |
| validator.js isIdentityCard(x, "ES") 13.15.35 | 18.4× as fast | |
| @kreyo/nif-validator 0.1.0 | 21.7× as fast | |
| jsvat 2.5.4 | 36.2× as fast | |
| stdnum 1.12.6 | 146.0× as fast |
No other library was faster on the NIE set.
| Library | M ops/s | This build is |
|---|---|---|
| nif-dni-nie-cif-validation this build | — | |
| @maistik/validate-nif 2.0.1 | 7.1× as fast | |
| nif-dni-nie-cif-validation 1.0.11 | 7.6× as fast | |
| spain-id 1.1.14 | 12.2× as fast | |
| @kreyo/nif-validator 0.1.0 | 22.0× as fast | |
| jsvat 2.5.4 | 23.2× as fast | |
| stdnum 1.12.6 | 146.7× as fast | |
| better-dni 4.4.2 | unsupported | unsupported |
| dni-js 1.0.0 | unsupported | unsupported |
| validator.js isIdentityCard(x, "ES") 13.15.35 | unsupported | unsupported |
| validator.js isTaxID(x, "es-ES") 13.15.35 | unsupported | unsupported |
No other library was faster on the CIF set.
Speed is not everything, and these figures leave things out: memory, the first call before the JIT warms up, input that needs normalization, and the cost of validate()’s detailed result. A library that rejects a string early is fast on it, which is why the agreement with SPEC.md is measured separately, below.
Bundle size
Section titled “Bundle size”What one validator adds to a bundle, minified and gzipped (min+gzip, lower is smaller), bundled with esbuild as the library’s own size check does. The bar of a library much larger than the others is drawn full, with a broken end.
| Library | Any type |
|---|---|
| @maistik/validate-nif 2.0.1 | |
| @kreyo/nif-validator 0.1.0 | |
| spain-id 1.1.14 | |
| dni-js 1.0.0 | |
| nif-dni-nie-cif-validation this build | |
| better-dni 4.4.2 | |
| jsvat 2.5.4 | |
| nif-dni-nie-cif-validation 1.0.11 | |
| validator.js isIdentityCard(x, "ES") 13.15.35 | |
| validator.js isTaxID(x, "es-ES") 13.15.35 | |
| stdnum 1.12.6 |
Smaller than this build for one validator of any type: @maistik/validate-nif 2.0.1 (456 B), @kreyo/nif-validator 0.1.0 (533 B), spain-id 1.1.14 (583 B) and dni-js 1.0.0 (882 B). Also measured: stdnum 1.12.6 with a deep import of its Spanish NIF module (not documented), 2,468 B.
Some smaller libraries validate fewer types. This one spends bytes on input normalization, the CIF-3 control types and K/L/M NIFs. Libraries published only as CommonJS can’t be tree-shaken, so one of their functions costs the whole library. Multi-country and multi-purpose libraries, such as stdnum and validator.js, are larger by design.
| Library | DNI | NIE | CIF | Any type | Whole library |
|---|---|---|---|---|---|
| @maistik/validate-nif 2.0.1 | 215 B | 243 B | 319 B | 456 B | 1,189 B |
| @kreyo/nif-validator 0.1.0 | 533 B | 533 B | 533 B | 533 B | 746 B |
| spain-id 1.1.14 | 165 B | 237 B | 363 B | 583 B | 704 B |
| dni-js 1.0.0 | 883 B | 882 B | unsupported | 882 B | 880 B |
| nif-dni-nie-cif-validation this build | 623 B | 586 B | 568 B | 929 B | 4,679 B |
| better-dni 4.4.2 | 1,135 B | 1,135 B | unsupported | 1,134 B | 1,132 B |
| jsvat 2.5.4 | 1,186 B | 1,186 B | 1,186 B | 1,186 B | 5,266 B |
| nif-dni-nie-cif-validation 1.0.11 | 1,517 B | 1,517 B | 1,518 B | 1,517 B | 1,514 B |
| validator.js isIdentityCard(x, "ES") 13.15.35 | 2,539 B | 2,539 B | unsupported | 2,539 B | 44,449 B |
| validator.js isTaxID(x, "es-ES") 13.15.35 | 6,537 B | 6,537 B | unsupported | 6,537 B | 44,449 B |
| stdnum 1.12.6 | 51,063 B | 51,063 B | 51,063 B | 51,063 B | 51,054 B |
Agreement with SPEC.md
Section titled “Agreement with SPEC.md”The share of the fixtures of the library’s test suite (default options) that each library judges as SPEC.md does: valid or invalid.
| Library | All fixtures | Canonical input only |
|---|---|---|
| nif-dni-nie-cif-validation this build | ||
| nif-dni-nie-cif-validation 1.0.11 | ||
| spain-id 1.1.14 | ||
| better-dni 4.4.2 | ||
| dni-js 1.0.0 | ||
| stdnum 1.12.6 | ||
| validator.js isIdentityCard(x, "ES") 13.15.35 | ||
| validator.js isTaxID(x, "es-ES") 13.15.35 | ||
| @maistik/validate-nif 2.0.1 | ||
| @kreyo/nif-validator 0.1.0 | ||
| jsvat 2.5.4 |
| Library | DNI | NIE | CIF | K/L/M | Disagreements | On a documented decision |
|---|---|---|---|---|---|---|
| nif-dni-nie-cif-validation this build | 100.0% | 100.0% | 100.0% | 100.0% | 0 | 0 |
| nif-dni-nie-cif-validation 1.0.11 | 73.3% | 88.9% | 83.6% | 100.0% | 20 | 13 |
| spain-id 1.1.14 | 90.0% | 77.8% | 86.9% | 36.4% | 22 | 13 |
| better-dni 4.4.2 | 73.3% | 77.8% | unsupported | 45.5% | 18 | 7 |
| dni-js 1.0.0 | 76.7% | 77.8% | unsupported | 45.5% | 17 | 7 |
| stdnum 1.12.6 | 90.0% | 83.3% | 63.9% | 100.0% | 29 | 29 |
| validator.js isIdentityCard(x, "ES") 13.15.35 | 80.0% | 77.8% | unsupported | 45.5% | 16 | 6 |
| validator.js isTaxID(x, "es-ES") 13.15.35 | 80.0% | 77.8% | unsupported | 100.0% | 10 | 5 |
| @maistik/validate-nif 2.0.1 | 86.7% | 77.8% | 86.9% | 45.5% | 22 | 14 |
| @kreyo/nif-validator 0.1.0 | 86.7% | 77.8% | 86.9% | 45.5% | 22 | 14 |
| jsvat 2.5.4 | 90.0% | 83.3% | 82.0% | 100.0% | 17 | 17 |
A type that a library doesn’t validate is left out of its figures, so compare the columns rather than “All fixtures” across libraries of a different scope. The list of disagreements of each library, with an example of each, is in latest.md.
Method
Section titled “Method”- Inputs. Generated from a seeded pseudo-random generator, so every run and every machine measures the same strings: valid documents and the same documents with a wrong control character, for each type.
- Calls. Each library is called exactly as its documentation shows, with its default options, on the raw input string. The one exception is jsvat, a VAT validator, whose inputs get an
ESprefix, added outside the timed loop. - Timing. tinybench runs each task in its own process, after a warmup, so what V8 learns from one library can’t change the next one’s figure. The whole run repeats in rounds, the order of the libraries rotates between sets and rounds, and each figure is its median round.
- Size. The single-function import that each library documents, bundled and minified with esbuild and gzipped at level 9, minus the cost of an empty import.
- Agreement. Every library’s “any type” call on the fixtures that test the default options, each fixture judged valid or invalid.
The full methodology, its caveats and the schema of the results are in bench/README.md.
Run it yourself
Section titled “Run it yourself”The libraries are installed at exact versions, so a run measures exactly what latest.json lists. Close other programs first, and compare ratios rather than absolute numbers:
git clone https://github.com/josegoval/nif-dni-nie-cif-validation.gitcd nif-dni-nie-cif-validationpnpm install --frozen-lockfilepnpm bench:competitors # about 5 minutes; writes bench/results/latest.json and latest.mdpnpm bench compares every validator with version 1 instead. A GitHub Actions workflow runs the same benchmark on demand, on a shared runner, and uploads the results.