I built a TypeScript client for Ceph Object Storage because the only npm package was 7 years old. Here’s What I Learned

Last month I went looking for a Node.js client to manage users and buckets on our Ceph RADOS Gateway. Found one package on npm — rgw-admin-client. Last published in 2019. No TypeScript. No ESM. No maintenance.

So I built my own.

What even is…


This content originally appeared on DEV Community and was authored by Himanshu Kumar

Last month I went looking for a Node.js client to manage users and buckets on our Ceph RADOS Gateway. Found one package on npm — rgw-admin-client. Last published in 2019. No TypeScript. No ESM. No maintenance.

So I built my own.

What even is Ceph RGW?

If you've worked with Kubernetes storage, you've probably bumped into Ceph. It's the storage backend behind Rook-Ceph and Red Hat OpenShift Data Foundation. The RADOS Gateway (RGW) is its S3-compatible object storage layer.

RGW has an Admin API to manage users, buckets, quotas, rate limits, and usage stats. But to use it from Node.js, you either:

Shell out to the radosgw-admin CLI inside a Ceph toolbox pod
Write raw HTTP requests with AWS SigV4 signing by hand
Use that 7-year-old unmaintained package and hope for the best

None of those felt great.

What I built

npm install radosgw-admin

import { RadosGWAdminClient } from 'radosgw-admin';

const rgw = new RadosGWAdminClient({
  host: 'http://ceph-rgw.example.com',
  port: 8080,
  accessKey: 'ADMIN_ACCESS_KEY',
  secretKey: 'ADMIN_SECRET_KEY',
});

// create a user
const user = await rgw.users.create({
  uid: 'alice',
  displayName: 'Alice',
  email: 'alice@example.com',
});

// set a 10GB quota (yes, you can just write "10G")
await rgw.quota.setUserQuota({
  uid: 'alice',
  maxSize: '10G',
  maxObjects: 50000,
});

// list all buckets
const buckets = await rgw.buckets.list();

8 modules. 39 methods. Zero runtime dependencies.

Why zero dependencies?
The RGW Admin API uses AWS Signature V4 for auth. Most people would reach for aws-sdk or @aws-sdk/signature-v4 for that. I didn't want to pull in a massive dependency tree for one signing function.

So I wrote the SigV4 implementation using just node:crypto. It's about 80 lines. The whole package has zero production dependencies — your node_modules stays clean.

Things I actually cared about while building this
TypeScript that helps, not annoys
The whole codebase runs with strict: true, noImplicitAny, exactOptionalPropertyTypes, and noUncheckedIndexedAccess. No any anywhere.

What this means for you — autocomplete works properly, types match what the API actually returns, and you catch mistakes before running anything.

snake_case in, camelCase out
The RGW API returns JSON with snake_case keys. Every JavaScript developer expects camelCase. The client transforms both directions automatically:

// you write camelCase
await rgw.users.create({ displayName: 'Alice', maxBuckets: 10 });

// the API receives: display-name=Alice&max-buckets=10

// the API returns: { "user_id": "alice", "display_name": "Alice" }

// you get back camelCase
user.userId;      // "alice"
user.displayName; // "Alice"

You never think about it.

Errors you can actually catch
Instead of generic "Request failed with status 404", you get typed errors:

import { RGWNotFoundError, RGWAuthError } from 'radosgw-admin';

try {
  await rgw.users.get('nonexistent');
} catch (error) {
  if (error instanceof RGWNotFoundError) {
    // user doesn't exist — handle it
  }
  if (error instanceof RGWAuthError) {
    // bad credentials — log and alert
  }
}

There's RGWNotFoundError, RGWAuthError, RGWConflictError, RGWValidationError, and a base RGWError. Validation errors are thrown before any HTTP call — so you don't waste a round trip on bad input.

Size strings that make sense
Quota methods accept human-readable sizes:

await rgw.quota.setUserQuota({ uid: 'alice', maxSize: '10G' });
// internally converts to 10737418240 bytes

You can write '500M', '1T', '1.5G' — whatever makes sense. Or pass raw bytes if you prefer.

The hard parts
SigV4 signing — AWS Signature V4 has a very specific signing process. Canonical request, string to sign, signing key derived from date + region + service. Getting the exact byte-level match right took a few days of reading the AWS docs and comparing against known-good signatures.

Ceph's response formats — Some endpoints return a JSON array on success but an XML error on failure. Some return empty body for success. The GET /bucket endpoint changes its response shape depending on whether you pass max-entries or not. Each of these needed special handling.

Dual ESM + CJS — Shipping a package that works with both import and require() in 2026 is still annoying. I used tsup to build both formats with correct exports mapping in package.json. Validated with publint and @arethetypeswrong/cli in CI.

What's in the box

Module What it does
Users Create, get, modify, delete, list, suspend, enable, stats
Keys Generate and revoke S3/Swift access keys
Subusers Create, modify, remove Swift subusers
Buckets List, info, delete, transfer ownership, verify index
Quota Get/set user and bucket quotas with size strings
Rate Limits Per-user, per-bucket, and global rate limiting
Usage Query bandwidth/ops reports, trim old logs
Info Cluster FSID and storage backend info

Every method has JSDoc with @example blocks, so your editor shows you exactly how to use it.

If you're running Rook-Ceph
Port-forward the RGW service and point the client at it:

kubectl port-forward svc/rook-ceph-rgw-my-store 8080:80 -n rook-ceph

const rgw = new RadosGWAdminClient({
  host: 'http://localhost',
  port: 8080,
  accessKey: '...', // from rook secret
  secretKey: '...',
});

Get the admin credentials:

kubectl get secret rook-ceph-dashboard-admin-gateway -n rook-ceph \
  -o jsonpath='{.data.accessKey}' | base64 -d

Numbers
280 tests passing
CI runs on Node 18, 20, and 22
90%+ code coverage
npm provenance with trusted publisher
Dual ESM + CJS with full type declarations

What's next
I'm planning to add multi-site/zone management and IAM role support in future versions. Open to suggestions — if you use the RGW Admin API and there's something missing, let me know.

Links:

npm: https://www.npmjs.com/package/radosgw-admin
GitHub: https://github.com/nycanshu/radosgw-admin
Docs: https://nycanshu.github.io/radosgw-admin

If you find it useful, a star on GitHub helps others find it too.


This content originally appeared on DEV Community and was authored by Himanshu Kumar


Print Share Comment Cite Upload Translate Updates
APA

Himanshu Kumar | Sciencx (2026-03-15T12:39:02+00:00) I built a TypeScript client for Ceph Object Storage because the only npm package was 7 years old. Here’s What I Learned. Retrieved from https://www.scien.cx/2026/03/15/i-built-a-typescript-client-for-ceph-object-storage-because-the-only-npm-package-was-7-years-old-heres-what-i-learned/

MLA
" » I built a TypeScript client for Ceph Object Storage because the only npm package was 7 years old. Here’s What I Learned." Himanshu Kumar | Sciencx - Sunday March 15, 2026, https://www.scien.cx/2026/03/15/i-built-a-typescript-client-for-ceph-object-storage-because-the-only-npm-package-was-7-years-old-heres-what-i-learned/
HARVARD
Himanshu Kumar | Sciencx Sunday March 15, 2026 » I built a TypeScript client for Ceph Object Storage because the only npm package was 7 years old. Here’s What I Learned., viewed ,<https://www.scien.cx/2026/03/15/i-built-a-typescript-client-for-ceph-object-storage-because-the-only-npm-package-was-7-years-old-heres-what-i-learned/>
VANCOUVER
Himanshu Kumar | Sciencx - » I built a TypeScript client for Ceph Object Storage because the only npm package was 7 years old. Here’s What I Learned. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2026/03/15/i-built-a-typescript-client-for-ceph-object-storage-because-the-only-npm-package-was-7-years-old-heres-what-i-learned/
CHICAGO
" » I built a TypeScript client for Ceph Object Storage because the only npm package was 7 years old. Here’s What I Learned." Himanshu Kumar | Sciencx - Accessed . https://www.scien.cx/2026/03/15/i-built-a-typescript-client-for-ceph-object-storage-because-the-only-npm-package-was-7-years-old-heres-what-i-learned/
IEEE
" » I built a TypeScript client for Ceph Object Storage because the only npm package was 7 years old. Here’s What I Learned." Himanshu Kumar | Sciencx [Online]. Available: https://www.scien.cx/2026/03/15/i-built-a-typescript-client-for-ceph-object-storage-because-the-only-npm-package-was-7-years-old-heres-what-i-learned/. [Accessed: ]
rf:citation
» I built a TypeScript client for Ceph Object Storage because the only npm package was 7 years old. Here’s What I Learned | Himanshu Kumar | Sciencx | https://www.scien.cx/2026/03/15/i-built-a-typescript-client-for-ceph-object-storage-because-the-only-npm-package-was-7-years-old-heres-what-i-learned/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.