imageimage
Schedule a Call

Get in Touch

  • Email Now
    contact@indusvalley.io
  • Headquarters
    Long Meadows Road Bedminster, New Jersey, 07921 United States
Social Link
  • Instagram
  • LinkedIn
  • X
  • Facebook
  • Youtube
  • Home
  • Services
    • AI Development
      • Generative AI
      • Machine Learning
      • Predictive Analytics
    • Mobile App Development
      • iOS App Development
      • Android App Development
      • Cross Platform App Development
    • Web Development
    • Digital Marketing
      • SEO
      • Social Media Marketing
      • Performance Marketing
      • Content Marketing
    • Design
      • UI/UX Design
      • Logo & Branding
      • Video Animation
    • IT Staff Augmentation
    • Cloud Services
  • IVY
  • Chat With IVY
  • Portfolio
  • Blogs
  • About Us
  • Contact Us
imageimage
image
  • Home
  • Services
    • AI Development
      • Generative AI
      • Machine Learning
      • Predictive Analytics
    • Mobile App Development
      • iOS App Development
      • Android App Development
      • Cross Platform App Development
    • Web Development
    • Digital Marketing
      • SEO
      • Social Media Marketing
      • Performance Marketing
      • Content Marketing
    • Design
      • UI/UX Design
      • Logo & Branding
      • Video Animation
    • IT Staff Augmentation
    • Cloud Services
  • IVY
  • Portfolio
  • Blogs
  • About Us
  • Contact Us
  • Sun-Tue (9:00 am-7.00 pm)
  • infoaploxn@gmail.com
  • +91 656 786 53
Get in Touch
Schedule a CallLet's Talk

Developer Insights & Best Practices / Type-Safe API Clients in 30 Lines of TypeScript

Type-Safe API Clients in 30 Lines of TypeScript
8/12/2025 | Ateeb Khan

Type-Safe API Clients in 30 Lines of TypeScript



The dream

I want to call an endpoint like this:

```

const user = await api.get(’/users/:id’, { params: { id: 42 } })
//     ^? User

```

No casting, no .json() dance, no stringly-typed params.

If I typo the path or forget a required query, the red squiggles save me before I hit save.



Step 1: describe the API in one place

We’ll use a small schema object instead of a swagger file:

```

// apiSchema.ts
export interface ApiSchema {
 ’/users/:id’: {
   get: {
     params: { id: number }
     resp: User
   }
 }
 ’/posts’: {
   get: {
     query: { page?: number }
     resp: Post[]
   }
   post: {
     body: CreatePost
     resp: Post
   }
 }

}


```

That’s it—no code-gen, no extra tooling.



Step 2: 30 lines of pure magic

```

// apiClient.ts
type Method = ’get’ | ’post’ | ’put’ | ’patch’ | ’delete’
 
type PathParams<Path extends string> =
 Path extends `${string}:${infer Param}/${infer Rest}`
   ? { [K in Param | keyof PathParams<Rest>]: string | number }
   : Path extends `${string}:${infer Param}`
   ? { [K in Param]: string | number }
   : never
 
type Endpoint<M extends Method, P extends keyof ApiSchema> =
 ApiSchema[P][M] extends { params: infer Pr }
   ? { params: Pr }
   : ApiSchema[P][M] extends { query: infer Q }
   ? { query: Q }
   : ApiSchema[P][M] extends { body: infer B }
   ? { body: B }
   : {}
 
async function apiCall<M extends Method, P extends keyof ApiSchema>(
 method: M,
 path: P,
 ...[args]: keyof Endpoint<M, P> extends never
   ? []
   : [opts: Endpoint<M, P>]
) {
 let url = path as string
 const opts = (args || {}) as any
 
 // 1. fill path params
 if (opts.params) {
   for (const [k, v] of Object.entries(opts.params)) {
     url = url.replace(`:${k}`, encodeURIComponent(String(v)))
   }
 }
 
 // 2. build fetch options
 const init: RequestInit = { method: method.toUpperCase() }
 if (opts.body) init.body = JSON.stringify(opts.body)
 if (opts.query) {
   url += ’?’ + new URLSearchParams(opts.query as any).toString()
 }
 
 // 3. fetch & validate
 const res = await fetch(url, init)
 if (!res.ok) throw new Error(res.statusText)
 return (await res.json()) as ApiSchema[P][M][’resp’]
}
 
// tiny wrappers for each verb
export const api = {
 get: <P extends keyof ApiSchema>(p: P, ...a: any[]) => apiCall(’get’, p, ...a),
 post: <P extends keyof ApiSchema>(p: P, ...a: any[]) => apiCall(’post’, p, ...a),
 // …add the rest if you need them
}


```

Count the lines—29 without comments.

Drop it in your project and you’re done.



 

Step 3: use it like a pro

```

// anywhere.ts
const user = await api.get(’/users/:id’, { params: { id: 42 } })
//     ^? User
 
const posts = await api.get(’/posts’, { query: { page: 2 } })
//     ^? Post[]
 
const newPost = await api.post(’/posts’, { body: { title: ’Hello TS’ } })
//     ^? Post


```

Misspell id? TS yells.

Forget the body? TS yells.

Wrong HTTP verb? TS yells.

It’s like GraphQL, minus the schema language.



What about runtime?

Because we still call fetch, everything works in the browser or Node.

Add zod or io-ts inside apiCall if you want runtime validation too—still under 50 lines total.



Ship it

Copy the two files above, adjust ApiSchema to your endpoints, and delete every hand-rolled fetch wrapper cluttering your repo.

Type-safe, zero deps, and you wrote it yourself in the time it took to sip a coffee.

Not bad for 30 lines.

 

Related Blogs

Explore More
How I Fixed My Flutter Dependency Chaos (And Made My App Faster)
  • September 09, 2025

How I Mastered Flutter Dependencies and Made My App Super Scalable

Node.js 101: How to Build Your First HTTP Server
  • September 03, 2025

Node.js 101: How to Build Your First HTTP Server

Kill the Boilerplate: One Line to Replace 200 Redux API Files
  • August 06, 2025

Let’s Stop Writing the Same 5 Lines of Boilerplate

Our Trusted
Partner.

Unlock Valuable Cloud and Technology Credits

Imagine reducing your operational costs by up to $100,000 annually without compromising on the technology you rely on. Through our partnerships with leading cloud and technology providers like AWS (Amazon Web Services), Google Cloud Platform (GCP), Microsoft Azure, and Nvidia Inception, we can help you secure up to $25,000 in credits over two years (subject to approval).

These credits can cover essential server fees and offer additional perks, such as:

  • Google Workspace accounts
  • Microsoft accounts
  • Stripe processing fee waivers up to $25,000
  • And many other valuable benefits

Why Choose Our Partnership?

By leveraging these credits, you can significantly optimize your operational expenses. Whether you're a startup or a growing business, the savings from these partnerships ranging from $5,000 to $100,000 annually can make a huge difference in scaling your business efficiently.

The approval process requires company registration and meeting specific requirements, but we provide full support to guide you through every step. Start saving on your cloud infrastructure today and unlock the full potential of your business.

exclusive-partnersexclusive-partners
E-Commerce

Shopify

Hosting

Hostinger

Technology

Sentry

CMS

Hubspot

MARKETING

Semrush

HOSTING

Namecheap

Productivity

Evernote

Hosting

Bluehost

Success Stories

Explore More

Fynder.AI

Underdog Apparel

Toast DXB

Let's TALK

Let's TALK and bring your ideas to life! Our experienced team is dedicated to helping your business grow and thrive. Reach out today for personalized support or request your free quote to kickstart your journey to success.

Connect Us
Contact Now
DIGITAL PRODUCTUI/UX DESIGNDIGITAL STUDIOBRANDING DESIGNUI/UX DESIGNEMAIL MARKETINGBRANDING DESIGNUI/UX DESIGNEMAIL MARKETING
DIGITAL PRODUCTUI/UX DESIGNDIGITAL STUDIOBRANDING DESIGN

Subscribe our newsletter

Company

  • About Us
  • Portfolio
  • Blogs
  • IVY
  • Services
  • Contact Us
UI/UX DESIGN
EMAIL MARKETING
BRANDING DESIGN
UI/UX DESIGN
EMAIL MARKETING

Our Services

  • AI Development
  • Web Development
  • Mobile App Development
  • Digital Marketing
  • IT Staff Augmentation
  • Facebook
  • Youtube
  • X
  • Linkedin
  • Instagram
footer-logo
  • Email Now
    contact@indusvalley.io

Copyright © 2025 Indus Valley Technologies | All rights reserved ®

Terms & ConditionsPrivacy Policy