Skip to content

Commit f9b9a41

Browse files
committed
feat: api config object
1 parent 5e81840 commit f9b9a41

File tree

9 files changed

+111
-131
lines changed

9 files changed

+111
-131
lines changed

.changeset/little-trains-brush.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@ddadaal/next-typed-api-routes-runtime": minor
3+
"@ddadaal/next-typed-api-routes-cli": minor
4+
---
5+
6+
Add api config object used to configure API client

README.md

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,24 @@ export default route<TestApiSchema>("TestApiSchema", async (req) => {
7070
});
7171
```
7272

73-
3. Run `npx ntar schema && npx ntar client`
73+
3. Create `apiClient` object
74+
75+
Create `src/apis/client.ts` with the following content
76+
77+
```ts
78+
import { createApiClient } from "@ddadaal/next-typed-api-routes-runtime";
79+
80+
// Pass custom client config here
81+
export const apiClient = createApiClient({});
82+
```
83+
84+
4. Run `npx ntar schema && npx ntar client`
7485

7586
`api-routes-schemas.json` will be generated at cwd. You should never edit it directly. The project cannot start without this file.
7687

7788
`src/apis/api.ts` will be generated at `src/apis`.
7889

79-
4. Import the `api` variable from `src/apis/api.ts` to use the client.
90+
5. Import the `api` variable from `src/apis/api.ts` to use the client.
8091

8192
```ts
8293
import { api } from "src/apis/api";
@@ -146,39 +157,6 @@ interface TestSchema {
146157
}
147158
```
148159

149-
# Custom basePath
150-
151-
Custom basePath is now supported. The generated API client will use `NEXT_PUBLIC_BASE_PATH` env for the base path for all the api routes. If you are upgrading from an older version, you should run `npx ntar client` to regenerate the api file to leverage this feature.
152-
153-
To keep the basePath config consistent in both Next.js and this library, you can set `basePath: process.env.NEXT_PUBLIC_BASE_PATH` in the `next.config.js`, and use the `NEXT_PUBLIC_BASE_PATH` env. Check out [the Next.js doc](https://nextjs.org/docs/api-reference/next.config.js/basepath) to configure basePath for Next.js.
154-
155-
```js
156-
// next.config.js
157-
module.exports = {
158-
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
159-
}
160-
```
161-
162-
You can also use `--basePathVar` option of `ntar` cli to customize the value of base path.
163-
164-
For example, when running `npx ntar client --basePathVar publicConfig.BASE_PATH`, the client file will look like:
165-
166-
```tsx
167-
import { fromApi } from "@ddadaal/next-typed-api-routes-runtime/lib/client";
168-
import { join } from "path";
169-
170-
import type { LoginSchema } from "src/pages/api/login/[username]";
171-
172-
// the value of basePath is the same as the basePathVar option
173-
const basePath = publicConfig.BASE_PATH;
174-
175-
export const api = {
176-
login: fromApi<LoginSchema>("GET", join(basePath, "/api/login/[username]")),
177-
};
178-
```
179-
180-
If your `basePath` declaration needs imports, you can also use `extraImports` cli option to add extra imports into the client file. Multiple `extraImports` options can be specified.
181-
182160
# Tips
183161

184162
- All schemas and used models must have globally unique name

example/src/apis/api.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
11
/* eslint-disable max-len */
22

3-
import { fromStaticRoute, fromTypeboxRoute, fromZodRoute } from "@ddadaal/next-typed-api-routes-runtime/lib/client";
4-
import { join } from "path";
3+
import { apiClient } from "src/apis/client";
54
import type { LoginSchema } from "src/pages/api/login/[username]";
65
import type { RegisterSchema } from "src/pages/api/register/index";
76
import type { TypeboxRouteSchema } from "src/pages/api/typeboxRoute/[test]";
87
import type { ZodRouteSchema } from "src/pages/api/zodRoute/[test]";
98

109

11-
12-
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
13-
14-
1510
export const api = {
16-
login: fromStaticRoute<LoginSchema>("GET", join(basePath, "/api/login/[username]")),
17-
register: fromStaticRoute<RegisterSchema>("POST", join(basePath, "/api/register")),
18-
typeboxRoute: fromTypeboxRoute<typeof TypeboxRouteSchema>("POST", join(basePath, "/api/typeboxRoute/[test]")),
19-
zodRoute: fromZodRoute<typeof ZodRouteSchema>("POST", join(basePath, "/api/zodRoute/[test]")),
11+
login: apiClient.fromStaticRoute<LoginSchema>("GET", "/api/login/[username]"),
12+
register: apiClient.fromStaticRoute<RegisterSchema>("POST", "/api/register"),
13+
typeboxRoute: apiClient.fromTypeboxRoute<typeof TypeboxRouteSchema>("POST", "/api/typeboxRoute/[test]"),
14+
zodRoute: apiClient.fromZodRoute<typeof ZodRouteSchema>("POST", "/api/zodRoute/[test]"),
2015
};
21-

example/src/apis/client.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { createApiClient } from "@ddadaal/next-typed-api-routes-runtime";
2+
3+
export const apiClient = createApiClient({
4+
baseUrl: typeof window === "undefined" ? `http://localhost:${process.env.PORT || 3000}` : "",
5+
});

packages/cli/src/generateClients.ts

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ interface Endpoint {
3434
importName: string;
3535
type: ApiType;
3636
method: string;
37-
url: string;
37+
path: string;
3838
}
3939

4040
async function getApiObject(
@@ -143,7 +143,7 @@ async function getApiObject(
143143
importName: found.typeName,
144144
functionName,
145145
type: found.apiType,
146-
url: "/api/" + relativePath + (filename === "index" ? "" : ("/" + filename)),
146+
path: "/api/" + relativePath + (filename === "index" ? "" : ("/" + filename)),
147147
});
148148
}
149149
} else {
@@ -155,19 +155,17 @@ async function getApiObject(
155155
export interface GenerateApiClientsArgs {
156156
apiFilePath?: string;
157157
apiRoutesPath?: string;
158-
fetchImport?: string;
158+
clientObjectName: string;
159+
clientObjectImportPath: string;
159160
apiObjectName?: string;
160-
basePathVar?: string;
161-
extraImports?: string[],
162161
}
163162

164163
export async function generateClients({
165164
apiFilePath = "src/apis/api.ts",
166165
apiRoutesPath = "src/pages/api",
167-
fetchImport = "@ddadaal/next-typed-api-routes-runtime/lib/client",
166+
clientObjectName,
167+
clientObjectImportPath,
168168
apiObjectName = "api",
169-
basePathVar = "process.env.NEXT_PUBLIC_BASE_PATH || \"\"",
170-
extraImports = [],
171169
}: GenerateApiClientsArgs) {
172170

173171
if (!apiRoutesPath.endsWith("/")) {
@@ -182,31 +180,23 @@ export async function generateClients({
182180

183181
await getApiObject(apiRoutesPath, "", endpoints, imports);
184182

185-
const basePathVarDeclaration = `const basePath = ${basePathVar};`;
186-
187183
// use string instead of ts factories to easily style the code and reduce complexity
188184
const apiObjDeclaration = `
189185
export const ${apiObjectName} = {
190186
${endpoints.map((e) => {
191187
192188
const { libImport, typeFormat } = ApiTypes[e.type];
193189
return (
194-
` ${e.functionName}: ${libImport}<${typeFormat(e.importName)}>("${e.method}", join(basePath, "${e.url}")),`
190+
` ${e.functionName}: ${clientObjectName}.${libImport}<${typeFormat(e.importName)}>` +
191+
`("${e.method}", "${e.path}"),`
195192
);
196193
},
197194
).join(EOL)}
198195
};
199196
`;
200197

201-
const importsFromRootPackage = new Set<string>();
202-
203-
for (const endpoint of endpoints) {
204-
importsFromRootPackage.add(ApiTypes[endpoint.type].libImport);
205-
}
206-
207198
const fetchApiImportDeclaration = `
208-
import { ${Array.from(importsFromRootPackage.values()).join(", ")} } from "${fetchImport}";
209-
import { join } from "path";
199+
import { ${clientObjectName} } from "${clientObjectImportPath}";
210200
`;
211201

212202
const importDeclarations = imports.map(({ relativePath, interfaceName }) => (
@@ -219,10 +209,6 @@ import { join } from "path";
219209
EOL + EOL +
220210
importDeclarations +
221211
EOL + EOL +
222-
extraImports.join(EOL) +
223-
EOL + EOL +
224-
basePathVarDeclaration +
225-
EOL + EOL +
226212
apiObjDeclaration;
227213

228214
// create dir if not exists

packages/cli/src/index.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,9 @@ yargs(hideBin(process.argv))
2222
default: "src/apis/api.ts",
2323
},
2424
apiRoutesPath: { type: "string", default: "src/pages/api" },
25-
fetchImport: {
26-
type: "string",
27-
default: "@ddadaal/next-typed-api-routes-runtime/lib/client",
28-
},
25+
clientObjectName: { type: "string", default: "apiClient" },
26+
clientObjectImportPath: { type: "string", default: "src/apis/client" },
2927
apiObjectName: { type: "string", default: "api" },
30-
basePathVar: { type: "string", default: "process.env.NEXT_PUBLIC_BASE_PATH || \"\"" },
31-
extraImports: { type: "array", string: true, default: []},
3228
});
3329
}, (argv) => {
3430
generateClients(argv);

packages/runtime/src/fetch/client.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { TypeboxRouteSchema, TypeboxRouteSchemaToSchema } from "../route/typeboxRoute";
2+
import { ZodRouteSchema, ZodRouteSchemaToSchema } from "../route/zodRoute";
3+
import { AnySchema, RequestArgs } from "../types";
4+
import { HttpMethod, jsonFetch, JsonFetchResultPromiseLike } from "./fetch";
5+
import { replacePathArgs } from "./replacePathArgs";
6+
import { removeNullOrUndefinedKey } from "./utils";
7+
8+
/**
9+
* Config for api client
10+
*/
11+
interface ApiClientConfig {
12+
/**
13+
* The baseUrl for all requests
14+
*/
15+
baseUrl?: string;
16+
17+
/**
18+
* Extra headers passed in every requests
19+
*/
20+
headers?: Record<string, string>;
21+
}
22+
23+
/**
24+
* Create a apiObject that will be used in generated API file
25+
* @param config Client config
26+
* @returns Api client that will be used in generated API file
27+
*/
28+
export const createApiClient = (config: ApiClientConfig) => {
29+
30+
function fromStaticRoute<TSchema extends AnySchema>(method: HttpMethod, url: string) {
31+
return function(
32+
args: RequestArgs<TSchema>,
33+
signal?: AbortSignal,
34+
): JsonFetchResultPromiseLike<TSchema> {
35+
36+
const anyArgs = args as any;
37+
// replace path params using query
38+
const replacedPath = anyArgs.query
39+
? replacePathArgs(url, anyArgs.query)
40+
: url;
41+
42+
return jsonFetch({
43+
url: (config.baseUrl ?? "") + replacedPath,
44+
method: method,
45+
query: removeNullOrUndefinedKey(anyArgs.query),
46+
body: anyArgs.body,
47+
headers: config.headers,
48+
}, signal);
49+
};
50+
}
51+
52+
function fromZodRoute<TSchema extends ZodRouteSchema>(method: HttpMethod, url: string) {
53+
return fromStaticRoute<ZodRouteSchemaToSchema<TSchema>>(method, url);
54+
}
55+
56+
function fromTypeboxRoute<TSchema extends TypeboxRouteSchema>(method: HttpMethod, url: string) {
57+
return fromStaticRoute<TypeboxRouteSchemaToSchema<TSchema>>(method, url);
58+
}
59+
60+
return {
61+
fromStaticRoute,
62+
fromZodRoute,
63+
fromTypeboxRoute,
64+
};
65+
};

packages/runtime/src/fetch/fetch.ts

Lines changed: 5 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
import { TypeboxRouteSchema, TypeboxRouteSchemaToSchema } from "../route";
2-
import { ZodRouteSchema, ZodRouteSchemaToSchema } from "../route/zodRoute";
3-
import type {
4-
Querystring, RequestArgs,
5-
} from "../types/request";
1+
import type { Querystring } from "../types/request";
62
import type { AnySchema, SuccessResponse } from "../types/schema";
73
import { failEvent, finallyEvent, prefetchEvent, successEvent } from "./events";
84
import { FETCH_ERROR, HttpError, TYPE_ERROR } from "./HttpError";
95
import { parseQueryToQuerystring } from "./parseQueryToQuerystring";
10-
import { replacePathArgs } from "./replacePathArgs";
11-
import { removeNullOrUndefinedKey } from "./utils";
126

137
function isServer() {
148
return typeof window === "undefined";
@@ -20,45 +14,29 @@ function isFormData(a: any): a is FormData {
2014

2115
export type HttpMethod = "GET" | "POST" | "DELETE" | "PATCH" | "PUT";
2216

23-
let token = "";
24-
25-
export function changeBearerToken(newToken: string): void {
26-
token = newToken;
27-
}
28-
2917

3018
export function fullFetch(
31-
path: string,
19+
url: string,
3220
query?: Querystring,
3321
init?: RequestInit,
3422
): Promise<Response> {
35-
const headers = token
36-
? { ...init?.headers, "authorization": `Bearer ${token}` }
37-
: init?.headers ?? {};
38-
39-
let url = path;
4023
if (query) {
4124
url += parseQueryToQuerystring(query);
4225
}
4326

44-
if (typeof window === "undefined") {
45-
url = `http://127.0.0.1:${process.env.PORT || 3000}${url}`;
46-
}
47-
4827
return fetch(url,
4928
{
50-
...init,
51-
headers,
5229
mode: "cors",
5330
// disable cache for IE11
5431
cache: "no-cache",
32+
...init,
5533
});
5634
}
5735

5836
export type FullFetch = typeof fullFetch;
5937

6038
export interface FetchInfo {
61-
path: string;
39+
url: string;
6240
method?: HttpMethod;
6341
query?: Querystring;
6442
body?: unknown;
@@ -206,7 +184,7 @@ export function jsonFetch<T extends AnySchema>(
206184

207185
prefetchEvent.execute(undefined);
208186

209-
return new JsonFetchResultPromiseLike(fullFetch(info.path, info.query, {
187+
return new JsonFetchResultPromiseLike(fullFetch(info.url, info.query, {
210188
method: info.method ?? "GET",
211189
headers: {
212190
...isForm ? undefined : { "content-type": "application/json" },
@@ -219,32 +197,3 @@ export function jsonFetch<T extends AnySchema>(
219197

220198
export type JsonFetch = typeof jsonFetch;
221199

222-
export function fromStaticRoute<TSchema extends AnySchema>(method: HttpMethod, url: string) {
223-
return function(
224-
args: RequestArgs<TSchema>,
225-
signal?: AbortSignal,
226-
): JsonFetchResultPromiseLike<TSchema> {
227-
228-
const anyArgs = args as any;
229-
// replace path params using query
230-
const replacedPath = anyArgs.query
231-
? replacePathArgs(url, anyArgs.query)
232-
: url;
233-
234-
return jsonFetch({
235-
path: replacedPath,
236-
method: method,
237-
query: removeNullOrUndefinedKey(anyArgs.query),
238-
body: anyArgs.body,
239-
}, signal);
240-
};
241-
}
242-
243-
export function fromZodRoute<TSchema extends ZodRouteSchema>(method: HttpMethod, url: string) {
244-
return fromStaticRoute<ZodRouteSchemaToSchema<TSchema>>(method, url);
245-
}
246-
247-
export function fromTypeboxRoute<TSchema extends TypeboxRouteSchema>(method: HttpMethod, url: string) {
248-
return fromStaticRoute<TypeboxRouteSchemaToSchema<TSchema>>(method, url);
249-
}
250-

packages/runtime/src/fetch/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from "./client";
12
export * from "./events";
23
export * from "./fetch";
34
export * from "./HttpError";

0 commit comments

Comments
 (0)