Skip to content

Instantly share code, notes, and snippets.

@byronferguson
Last active February 20, 2024 08:05
Show Gist options
  • Save byronferguson/fb808f6590ca328b5b4151738f8bb58a to your computer and use it in GitHub Desktop.
Save byronferguson/fb808f6590ca328b5b4151738f8bb58a to your computer and use it in GitHub Desktop.
HttpFactory for Repo pattern in Nuxt3
import type { $Fetch } from 'nitropack';
import type { AsyncDataOptions } from 'nuxt/app';
import type { FetchOptions } from 'ofetch';
import { joinURL } from 'ufo';
import { z } from 'zod';
export type { $Fetch };
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
type CallOptions<T> = {
asyncOptions?: AsyncDataOptions<T>;
fetchOptions?: Omit<FetchOptions, 'method' | 'body' | 'onRequest'>;
schema?: z.ZodType<T>;
useComposable?: boolean;
};
type GetOptions<T> = Omit<CallOptions<T>, 'fetchOptions'>;
export class HttpFactory {
private readonly fetch: $Fetch;
protected resource: string;
constructor(fetch: $Fetch, resource = '') {
this.fetch = fetch;
this.resource = resource;
}
protected async call<
R = unknown,
DTO extends BodyInit | Record<string, any> | null | undefined = Record<string, any>,
>(
method: Method,
url: string,
body?: DTO,
{ fetchOptions = {}, asyncOptions = {}, useComposable = true, schema }: CallOptions<R> = {},
) {
const fullPath = joinURL(this.resource, url);
const options = {
method,
body,
...fetchOptions,
};
if (useComposable) {
return useAsyncData(async () => {
const data = await this.fetch<R>(fullPath, options);
return schema ? schema.parse(data) : data;
}, asyncOptions);
}
const data = await this.fetch<R>(fullPath, options);
return schema ? schema.parse(data) : data;
}
/**
* A helper method for making GET requests.
*/
protected async get<R = unknown>(
method: Method,
url: string,
fetchOptions: Pick<CallOptions<R>, 'fetchOptions'>,
{ asyncOptions = {}, useComposable = true, schema }: GetOptions<R> = {},
) {
const fullPath = joinURL(this.resource, url);
const options = {
method,
...fetchOptions,
};
if (useComposable) {
return useAsyncData(async () => {
const data = await this.fetch<R>(fullPath, options);
return schema ? schema.parse(data) : data;
}, asyncOptions);
}
const data = await this.fetch<R>(fullPath, options);
return schema ? schema.parse(data) : data;
}
}
import camelcaseKeys from 'camelcase-keys';
import type { $Fetch, NitroFetchOptions, NitroFetchRequest } from 'nitropack';
import type { AsyncDataOptions } from 'nuxt/app';
import { joinURL } from 'ufo';
import { z } from 'zod';
export type { $Fetch };
type Method = NitroFetchOptions<NitroFetchRequest>['method'];
type CallOptions<T> = {
asyncOptions?: AsyncDataOptions<T>;
fetchOptions?: Omit<NitroFetchOptions<NitroFetchRequest>, 'method' | 'body' | 'onRequest'>;
schema?: z.ZodType<T>;
useComposable?: boolean;
};
type GetOptions<T> = Omit<CallOptions<T>, 'fetchOptions'>;
type JSONValue = string | number | boolean | { [key: string]: JSONValue } | Array<JSONValue>;
export class HttpFactory {
private readonly fetch: $Fetch;
protected resource: string;
constructor(fetch: $Fetch, resource = '') {
this.fetch = fetch;
this.resource = resource;
}
protected call<
R = unknown,
DTO extends BodyInit | Record<string, any> | null | undefined = Record<string, any>,
>(
method: Method,
url: string,
body?: DTO,
{ fetchOptions = {}, asyncOptions = {}, useComposable = true, schema }: CallOptions<R> = {},
) {
const fullPath = joinURL(this.resource, url);
const options = {
method,
body,
...fetchOptions,
};
if (useComposable) {
return useAsyncData(() => this.execute(fullPath, options, schema), asyncOptions);
}
return this.execute(fullPath, options, schema);
}
/**
* A helper method for making GET requests.
*/
protected get<R = unknown>(
method: Method,
url: string,
fetchOptions: Pick<CallOptions<R>, 'fetchOptions'>,
{ asyncOptions = {}, useComposable = true, schema }: GetOptions<R> = {},
) {
const fullPath = joinURL(this.resource, url);
const options = {
method,
...fetchOptions,
};
if (useComposable) {
return useAsyncData(() => this.execute(fullPath, options, schema), asyncOptions);
}
return this.execute(fullPath, options, schema);
}
private async execute<R>(
fullPath: string,
options: NitroFetchOptions<NitroFetchRequest>,
schema?: z.ZodType<R>,
): Promise<R> {
let data: any = await this.fetch<JSONValue>(fullPath, options);
if (fullPath.includes('/v1') && typeof data === 'object' && data !== null) {
data = camelcaseKeys(data as Record<string, unknown> | readonly Record<string, unknown>[], {
deep: true,
});
}
return schema ? schema.parse(data) : data;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment