17
Exploring the npm registry API
In this post, we will learn how to use the REST API provided by the npm registry to programmatically discover public Javascript packages and retrieve their metadata.
The term npm
refers both to:
- The
npm
CLI tool installed by default with Node.js on your machine - The npm registry, an online service which collects more than 1.6M public Javascript packages
For example, when running the npm install react
command in your Javascript project, you are downloading the react
package from the online npm registry.
In this post, we are interested in the online service and its public API.
While many people regularly use npm's website to discover packages, only a few know that npm also provides a public REST API accessible at registry.npmjs.org.
This API provides methods to:
- Get information about the registry itself
- Get all available information about a specific package
- Get information about a specific version of a package
- Search packages by text
- Count the number of downloads for packages
We can call these methods by:
- Sending HTTP requests to the correct endpoints
- Using a fully typed package I developed named query-registry
You can follow along online on this RunKit notebook or on your machine by installing the isomorphic-unfetch
and query-registry
packages as follows:
npm install isomorphic-unfetch query-registry
You can also use as references the official API specification and the documentation for query-registry.
Finally, you can explore the API and its responses in your browser by going to registry.npmjs.org.
If we want to know more about the underlying database used by the registry, we can send a GET
request to the /
endpoint, that is https://registry.npmjs.org/
.
async function example1WithFetch() {
const endpoint = "https://registry.npmjs.org/";
const res = await fetch(endpoint);
const data = await res.json();
console.log(data);
}
async function example1WithQueryRegistry() {
const data = await queryRegistry.getRegistryMetadata();
console.log(data);
}
We receive a response containing information about the registry's database, including its name and some interesting attributes, as shown below:
{
"db_name":"registry",
"engine":"couch_bt_engine",
"doc_count":2226548,
"doc_del_count":334,
"update_seq":5769731,
"purge_seq":0,
"compact_running":false,
"sizes":{
"active":57693928578,
"external":132154863659,
"file":58937123056
},
"disk_size":58937123056,
"data_size":57693928578,
"other":{
"data_size":132154863659
},
"instance_start_time":"1624686290809498",
"disk_format_version":7,
"committed_update_seq":5769731,
"compacted_seq":5729968,
"uuid":"964c127ddcbbd59982db296a0f9e8a56"
}
If we want to get a packument (package document) containing all the information available on a package, we can send a GET
request to the /<package>
endpoint, for example https://registry.npmjs.org/react or https://registry.npmjs.org/@types/node.
async function example2WithFetch(name) {
const endpoint = `https://registry.npmjs.org/${name}`;
const res = await fetch(endpoint);
const data = await res.json();
console.log(data);
}
async function example2WithQueryRegistry(name) {
const data = await queryRegistry.getPackument({ name });
console.log(data);
}
We receive a response containing all the data associated to a package, including its ID, name, description, author, license and manifests for each published version.
{
"_id": "react",
"_rev": "1684-29eba7dd741dee3c165b86b7e4f63461",
"name": "react",
"description": "React is a JavaScript library for building user interfaces.",
"dist-tags": {…},
"versions": {…},
"maintainers": […],
"time": {…},
"repository": {…},
"readme": "",
"readmeFilename": "",
"homepage": "https://reactjs.org/",
"keywords": […],
"bugs": {…},
"users": {…},
"license": "MIT"
}
If we want to get a package manifest containing information about a specific version of a package, for example [email protected]
or @types/[email protected]
, we can send a GET
request to the /<package>/<version>
endpoint, for example https://registry.npmjs.org/react/17.0.2 or https://registry.npmjs.org/@types/node/15.14.0.
async function example3WithFetch(name, version) {
const endpoint = `https://registry.npmjs.org/${name}/${version}`;
const res = await fetch(endpoint);
const data = await res.json();
console.log(data);
}
async function example3WithQueryRegistry(name, version) {
const data = await queryRegistry.getPackageManifest({ name, version });
console.log(data);
}
We receive a response containing data that describes a published version of a package.
This data consists of the contents of package.json
at publishing time plus some additional attributes added by the registry.
{
"name": "react",
"description": "React is a JavaScript library for building user interfaces.",
"keywords": […],
"version": "17.0.2",
"homepage": "https://reactjs.org/",
"bugs": {…},
"license": "MIT",
"main": "index.js",
"repository": {…},
"engines": {…},
"dependencies": {…},
"browserify": {…},
"_id": "[email protected]",
"_nodeVersion": "15.11.0",
"_npmVersion": "7.6.0",
"dist": {…},
"_npmUser": {…},
"directories": {},
"maintainers": […],
"_npmOperationalInternal": {…},
"_hasShrinkwrap": false,
}
If we want to search packages by text, we can send a GET
request to the /-/v1/search?text=<some query>
endpoint, for example https://registry.npmjs.org/-/v1/search?text=react.
We can also use special keyword parameters in our text query to improve our results. For example, to find packages that I published we can use the author:velut
keyword parameter like this: https://registry.npmjs.org/-/v1/search?text=author:velut.
The official API specification contains the full list of supported search criteria.
async function example4WithFetch(text) {
const endpoint = `https://registry.npmjs.org/-/v1/search?text=${text}`;
const res = await fetch(endpoint);
const data = await res.json();
console.log(data);
}
async function example4WithQueryRegistry(text) {
const data = await queryRegistry.searchPackages({ query: { text } });
console.log(data);
}
We receive a response containing a list of packages matching our query inside the objects
attribute. Each package comes with a small number of important attributes, including name
and version
, plus some score values for the package itself and for its relevance to our query.
{
"objects": [
{
"package": {
"name": "react",
"scope": "unscoped",
"version": "17.0.2",
"description": "React is a JavaScript library for building user interfaces.",
"keywords": ["react"],
"date": "2021-03-22T21:56:19.536Z",
"links": {
"npm": "https://www.npmjs.com/package/react",
"homepage": "https://reactjs.org/",
"repository": "https://github.com/facebook/react",
"bugs": "https://github.com/facebook/react/issues"
},
"publisher": {
"username": "…",
"email": "…"
},
"maintainers": [
{ "username": "…", "email": "…" },
{ "username": "…", "email": "…" }
]
},
"score": {
"final": 0.5866665170132767,
"detail": {
"quality": 0.5246016720020373,
"popularity": 0.8931981392742823,
"maintenance": 0.3333333333333333
}
},
"searchScore": 100000.63
}
],
"total": 164637,
"time": "Fri Jul 02 2021 13:13:14 GMT+0000 (Coordinated Universal Time)"
}
If we want to count the number of downloads for a package in a given time period, we can send a GET
request to a slightly different API endpoint at https://api.npmjs.org/downloads/point/<period>/<package>
, for example https://api.npmjs.org/downloads/point/last-week/react. Supported time periods include last-day
, last-week
, last-month
and last-year
.
The download counts API also provides other methods to count downloads for packages and for the whole registry.
async function example5WithFetch(name, period) {
const endpoint = `https://api.npmjs.org/downloads/point/${period}/${name}`;
const res = await fetch(endpoint);
const data = await res.json();
console.log(data);
}
async function example5WithQueryRegistry(name, period) {
const data = await queryRegistry.getPackageDownloads({ name, period });
console.log(data);
}
We receive a simple response containing the package's name, its total number of downloads and the start and end dates for the selected time period.
{
"downloads": 10889040,
"start": "2021-06-25",
"end": "2021-07-01",
"package": "react"
}
Sometimes we may want to use a proxy or mirror of the npm registry instead of the original registry itself. For example, Cloudflare provides a mirror at https://registry.npmjs.cf with CORS enabled, allowing us to query the registry directly from the browser or client-side applications.
For example, try pasting this snippet in your browser's console:
fetch("https://registry.npmjs.org/react").then(res => res.json()).then(console.log)
It should fail with a CORS error because it's using the original registry. Instead, the following snippet should work because it's using Cloudflare's registry mirror.
fetch("https://registry.npmjs.cf/react").then(res => res.json()).then(console.log)
We can use the same endpoints available on registry.npmjs.org provided that they are supported by the chosen mirror registry.
async function bonusWithFetch(name) {
const endpoint = `https://registry.npmjs.cf/${name}`;
const res = await fetch(endpoint);
const data = await res.json();
console.log(data);
}
async function bonusWithQueryRegistry(name, registry) {
const data = await queryRegistry.getPackument({ name, registry });
console.log(data);
}
The responses should be the same as the ones provided by the original npm registry, maybe slightly delayed due to the mirroring process.
In this post, we have learnt what is npm, how we can use its public API to discover and analyze public Javascript packages and how we can take advantage of API client wrappers such as query-registry and registry mirrors such as https://registry.npmjs.cf to improve our interactions with this API both in server-side and client-side Javascript applications.
Be sure to leave a comment if you have any doubts or if you end up building something interesting using this lesser known but powerful API.
If you liked this article and want to know when I post more, you can follow me on Twitter.
- Cover photo by Ricardo Gomez Angel on Unsplash
17