Skip to content

Instantly share code, notes, and snippets.

@spikeninja
Created April 4, 2025 18:49
Show Gist options
  • Save spikeninja/cad294f65b73eafa2f262befc52eaa19 to your computer and use it in GitHub Desktop.
Save spikeninja/cad294f65b73eafa2f262befc52eaa19 to your computer and use it in GitHub Desktop.
axios-like wrapper over fetch
export class ApiClient {
private baseUrl: string
private config: RequestInit
private afterRequest: ((response: Response) => Response)[]
private beforeRequest: ((config: RequestInit) => RequestInit)[]
constructor({
baseUrl,
signal,
credentials,
}: {
baseUrl: string
credentials?: RequestCredentials
signal?: AbortSignal | null
}) {
this.baseUrl = baseUrl
this.afterRequest = []
this.beforeRequest = []
this.config = { credentials, signal }
}
private async request<T>(url: string, config: RequestInit): Promise<T> {
let modifiedConfig = config
for (const interceptor of this.beforeRequest) {
modifiedConfig = interceptor(modifiedConfig)
}
const response = await fetch(new URL(url, this.baseUrl), {
...modifiedConfig,
...this.config,
})
let modifiedResponse = response
for (const interceptor of this.afterRequest) {
modifiedResponse = interceptor(modifiedResponse)
}
return this.handleRespone<T>(modifiedResponse)
}
private async handleRespone<T>(response: Response): Promise<T> {
const contentType = response.headers.get("Content-Type") || ""
const isJson = contentType.includes("application/json")
const data = isJson ? await response.json() : await response.text()
if (!response.ok) {
if (isJson && data.errors !== null) {
throw new Error(JSON.stringify(data.errors))
}
throw new Error(data.message || response.statusText)
}
return data as T
}
addAfterRequestHandler(func: (response: Response) => Response) {
this.afterRequest.push(func)
}
addBeforeRequestHandler(func: (config: RequestInit) => RequestInit) {
this.beforeRequest.push(func)
}
async get<T>(url: string, payload: { headers?: HeadersInit }) {
const config: RequestInit = {
method: "GET",
headers: {
"Content-Type": "application/json",
...payload.headers,
},
}
return this.request<T>(url, config)
}
async post<T>(url: string, payload: { data: object; headers?: HeadersInit }) {
const config: RequestInit = {
method: "POST",
headers: {
"Content-Type": "application/json",
...payload.headers,
},
body: JSON.stringify(payload.data),
}
return this.request<T>(url, config)
}
async patch<T>(
url: string,
payload: { data: object; headers?: HeadersInit }
) {
const config: RequestInit = {
method: "PATCH",
headers: {
"Content-Type": "application/json",
...payload.headers,
},
body: JSON.stringify(payload.data),
}
return this.request<T>(url, config)
}
async put<T>(url: string, payload: { data: object; headers?: HeadersInit }) {
const config: RequestInit = {
method: "PUT",
headers: {
"Content-Type": "application/json",
...payload.headers,
},
body: JSON.stringify(payload.data),
}
return this.request<T>(url, config)
}
async delete<T>(url: string, payload: { headers: HeadersInit }) {
const config: RequestInit = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
...payload.headers,
},
}
return this.request<T>(url, config)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment