npm Registry
Lightweight, dependency-free, in-memory npm registry API fake for testing code that installs from, views, publishes to, or searches an npm-compatible registry (npm, pnpm, yarn).
Default port: 4776
Quick start
import { NpmRegistryServer } from "./services/npm-registry/src/server.js";
const server = new NpmRegistryServer(4776);
await server.start();
// ... run your app/tests ...
await server.stop();
Point npm at it:
npm config set registry http://127.0.0.1:4776
npm view left-pad # packument
npm view left-pad@1.3.0 # single version
Access via MCP / preview URL
- Registry base URL:
http://127.0.0.1:4776 - Set
NPM_REGISTRY_URL=http://127.0.0.1:4776andNPM_TOKEN=npm_parlel.
Reads are unauthenticated; PUT /:package (publish) requires Authorization: Bearer <token> or Basic.
Implemented operations
State is in-memory and ephemeral. Scoped package names (@scope/name) are %2f-encoded by clients and decoded here.
GET /:package— the packument:{ name, "dist-tags", versions, time, maintainers, ... }.GET /:package/:version— a single version manifest (also resolves dist-tags likelatest).PUT /:package— publish (npm publish); requires auth. Adds versions and appliesdist-tags.GET /-/v1/search?text=— registry search ({ objects, total, time }).
Each published version carries a faithful dist block (shasum, integrity sha512-..., tarball URL).
Service & inspection (parlel extensions)
GET /— service metadata.GET /health—{ status: "ok" }.POST /__parlel/reset— reset state.GET /__parlel/packages— list package names.
Surface coverage
This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.
Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.
| Feature | Status |
|---|---|
Packument GET /:package | ✅ Supported |
Single version GET /:package/:version (+ dist-tag) | ✅ Supported |
Publish PUT /:package (auth required) | ✅ Supported |
Scoped packages (@scope/name) | ✅ Supported |
Search GET /-/v1/search | ✅ Supported |
Faithful dist (shasum/integrity/tarball) | ✅ Supported |
Actual tarball (.tgz) bytes / download | ✓ By design — Intentional for a local, zero-cost test emulator |
dist-tag add/rm endpoints (/-/package/:pkg/dist-tags) | ⟳ Roadmap |
| Deprecate / unpublish / audit | ⟳ Roadmap |
| Token scope / publish-access enforcement | ✓ By design — Any non-empty credential is accepted — no real secrets needed |
Error codes & shapes
Error envelope: { "error": "..." }.
| Status | When |
|---|---|
401 | publish without auth |
400 | invalid publish payload |
404 | unknown package / version |
405 | method not allowed |
Manifest
See services/npm-registry/manifest.json:
- name:
npm-registry, image:parlel/npm-registry:1 - port:
4776, protocol:http, healthcheck:/health, startup ≈ 100ms - env:
NPM_REGISTRY_URL,NPM_TOKEN
Configuration — test.env
Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.
NPM_REGISTRY_URL=http://parlel-bridge:4776
NPM_TOKEN=npm_parlel
<!-- parlel:testenv:end -->