Skip to content
All posts
2 min read

What We Learned Building monimejs

Building a payment SDK taught us that the hard part isn't the API calls — it's everything around them. Retries, idempotency, types, and making failure obvious.

monimejs started as a weekend project. We were integrating with Monime’s payment API for a client project and found ourselves writing the same boilerplate for every endpoint — error handling, type definitions, retry logic. So we extracted it into a library.

Three weeks later, it was on npm with 12 resource modules, full TypeScript coverage, and a test suite. Here’s what we learned along the way.

The API calls are the easy part

Wrapping a REST API in TypeScript functions is straightforward. You read the docs, define the types, write the fetch calls. A junior developer could do it in a day. The hard part is everything else.

Retries need to be smarter than you think

Our first retry implementation was simple: if a request fails, wait a second and try again, up to 3 times. This is wrong for payment APIs.

Payment endpoints can fail in ways where retrying is dangerous — a timeout doesn’t mean the payment didn’t go through. So we implemented:

  • Exponential backoff — each retry waits longer than the last
  • Retry-After header support — if the API tells you when to retry, listen
  • Idempotency keys — auto-generated for every POST request so retries don’t create duplicate payments
  • Selective retry — only retry on network errors and 5xx responses, never on 4xx (those are your fault)

This quadrupled the code in our HTTP layer. But it’s the difference between a toy and a tool.

Types are documentation

Every response from monimejs is fully typed. Not just the success case — the error case too. When a payment fails, you get a typed error object with a code, message, and optional validationErrors array. You can switch on the error code in your IDE and get autocomplete.

We debated whether this level of typing was worth the maintenance cost. It is. The TypeScript compiler catches more integration bugs than our test suite does. When Monime changes their API, the types break before anything else — and that’s exactly what you want.

Make failure obvious

Every method in monimejs returns the same shape: { success: boolean, result?, messages }. There are no thrown exceptions for API errors. If a payment fails, you check success — you don’t wrap everything in try/catch.

We made this choice because payment integrations are critical paths. You should never accidentally ignore a payment failure because you forgot a try/catch block. The return type forces you to handle both cases.

AbortController support matters

This was an afterthought that became essential. In a SvelteKit app, if a user navigates away while a payment is processing, you need to cancel the in-flight request. Without AbortController support, you get orphaned requests and confusing UI states. Every SDK that makes HTTP calls should support cancellation from day one.

Ship it small

monimejs has zero runtime dependencies. The entire package is the SDK code, the types, and nothing else. No axios, no node-fetch, no lodash. We use the built-in fetch API everywhere.

For a library that lives in other people’s node_modules, every dependency you add is a dependency you’re imposing on them. Keep it minimal.