Skip to content

Commit

Permalink
chore(docs): add usage koa-router and axios-api-client
Browse files Browse the repository at this point in the history
  • Loading branch information
dirkdev98 committed Mar 26, 2023
1 parent 0e13d66 commit f898392
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 13 deletions.
17 changes: 15 additions & 2 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ function getHomeSidebar() {
link: "/generators/targets.html",
},
{
text: "API clients",
link: "/generators/api-clients.html",
text: "Generating from an structure",
link: "/generators/importing-structure.html",
},
{
text: "Build a custom structure",
Expand All @@ -140,6 +140,19 @@ function getHomeSidebar() {
},
],
},
{
text: "Using the generated code",
items: [
{
text: "Koa router",
link: "/generators/usage/koa-router.html",
},
{
text: "Axios API client",
link: "/generators/usage/axios-api-client.html",
},
],
},
],
},

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
# Generating API clients
# Importing a (remote) Compas structure or OpenAPI specification

Compas supports generating API clients for all previously mentioned
[targets](/generators/targets.html#api-clients). There are two main ways to get
started with API client generation

- Based on an OpenAPI specification
- Based on a (remote) Compas structure
Starting with the code generators requires a structure. Compas supports both
loading a (remote) Compas structure as well as converting and using an OpenAPI
specification.

## Importing an OpenAPI specification

To import an OpenAPI specification, make sure it is in JSON instead of YAML.
Then it can be converted to a Compas structure and added to the generator.
To import an OpenAPI specification, make sure it is in JSON OpenAPI 3.x format
instead of YAML. Then it can be converted to a Compas structure and added to the
generator.

```js {7-8}
import { readFileSync } from "fs";
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,5 @@ And a bunch more! Start discovering the Compas code generators by reading more
about

- [The supported targets](/generators/targets.html)
- [Generating API clients](/generators/api-clients.html)
- [Generating API client from an OpenAPI specification](/generators/importing-structure.html)
- [Or building your own structure](/generators/build-structure/types-and-validators.html)
2 changes: 1 addition & 1 deletion docs/generators/targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ generator.generate({
specification will be generated. This can be usefull when you utilize Compas
in libraries.

::: warning
::: tip

If the `targetLanguage` is set to `js`, TypeScript types will still be
generated. You can use these generated types in combination with the TypeScript
Expand Down
130 changes: 130 additions & 0 deletions docs/generators/usage/axios-api-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Axios API client

As seen at [the targets](/generators/targets.html) Compas supports generating
API clients based on Axios. Even though we support different target languages
(JavaScript and TypeScript) and runtimes (Node.js, browser, React-Native), the
generated interface looks practically the same over all of them. In this
documented we use the Typescript variant for the broswer runtime

```js
import { Generator } from "@compas/code-gen/experimental";

const generator = new Generator();

generator.generate({
targetLanguage: "ts",
outputDirectory: "./src/generated",
generators: {
apiClient: {
target: {
library: "axios",
targetRuntime: "browser",
globalClient: false,
includeWrapper: "react-query",
},
},
},
});
```

## Usage

Compas generates a specific, fully typed, function for each available route.

```ts
import { apiUserList } from "./generated/user/apiClient";
// declare function apiUserList(axiosInstance: AxiosInstance): Promise<UserListResponse>;

const { users } = await apiUserList(axiosInstance);

// Extra inputs like route params, query params or a request body is typed as well
// declare function apiUserUpdate(axiosInstance: AxiosInstance, params: { userId: string }, body: { receiveNotifications: boolean }): Promise<UserListResponse>;
await apiUserUpdate(
axiosInstance,
{ userId: "uuid-x" },
{ receiveNotifications: true },
);
```

## React-query wrapper

When `includeWrapper` is set to `react-query`, Compas will generate a wrapper
hook around each API client function using
[TanStack Query](https://tanstack.com/query/latest). The above examples can then
be used like

```tsx
import { ApiProvider } from "./generated/api-client";
import { useUserList, useUserUpdate } from "./generated/reactQueries";

function App() {
// Wrap your React tree with the generated ApiProvder
// This step is not necessary when `globalClients` is set to `true`, see below.
return (
<ApiProvider
axiosInstance={axios.create({
/* ... */
})}
>
<UserList />
</ApiProvider>
);
}

function UserList() {
const { data } = useUserList();
const { mutate: updateUser } = useUserUpdate(
{},
{
// Automatically invalidate dependant queries when they are provided in the structure
invalidateQueries: true,
},
);

// Use some form library
const { onSubmit } = useForm({
onSubmit: (values) => {
// Call the mutation
updateUser({
params: {
userId: values.selectedUser,
},
body: {
receiveNotifications: value.receiveNotifications,
},
});
},
});

return <div>{/* ... */}</div>;
}
```

## Global client usage

If the `globalClient` option is set to `true` in the generate call, Compas will
generate a `common/api-clients.ts` file. This contains a bare Axios (and
react-query `QueryClient`) instance. This removes the need to pass in the
`AxiosInstance` as the first argument on all generated API calls.

Use this with caution if you do things like server-side-rendering (SSR) for
authenticated pages, as the global client will be reused across different
requests from different users.

Overwriting options on global clients can be done as well:

```ts
import { QueryClient } from "@tanstack/react-query";
import { axiosInstance } from "./generated/api-clients";

axiosIntance.defaults.baseURL = "https://my.site";

// And if you use the react-query wrapper
import { queryClient } from "./generated/api-clients";

queryClient.setDefaultOptions({
queries: {
retry: 2,
},
});
```
103 changes: 103 additions & 0 deletions docs/generators/usage/koa-router.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Koa compatible router

As mentioned in [the targets](/generators/targets.html), Compas supports
generating a [Koa](https://koajs.com/) compatible router.

```js
import { Generator } from "@compas/code-gen/experimental";

const generator = new Generator();

// Build your own structure or import an existing one

generator.generate({
targetLanguage: "js",
outputDirectory: "./src/generated",
generators: {
router: {
target: {
library: "koa",
},
exposeApiStructure: true,
},
},
});
```

## Setup

```js
import Koa from "koa";
import { router } from "./src/generated/common/router.js";

const app = new Koa();

// Add your own middleware and custom route handlers
app.use((ctx, next) => {
console.log(ctx.method, ctx.path);
return next();
});

// And finally use the generated router middleware
app.use(router());

app.listen(3000);
```

All your routes are now be accessible but return a `405 Not Implemented` HTTP
status. Let's see how to add route implementations.

## Implementing controllers

Implementing the controllers is done by overwriting their generated
implementations.

```js
// Note how we import the handlers or controller from the generated files
import { userHandlers } from "./src/generated/user/controller.js";

userHandlers.single = async (ctx) => {
// ctx is a Koa context.
// Set the response, like you'd normally do in Koa.
ctx.body = {
email: "[email protected]",
};
};

import { imaginaryHandlers } from "./src/generated/imaginary/controller.js";

// Validators are integrated in to the generated router.
imaginaryHandlers.route = async (ctx) => {
// `R.get("/:userId", "route").params({ userId: T.uuid() })`
// `GET /e4dbc7dd-adfb-4fce-83f6-f5dcaf68c30c`
// results in `ctx.validatedParams`. The router will automatically return a `400 Bad Request` if the `userId` is not a valid uuid.
ctx.validatedParams.userId; // -> uuid

// `R.get("/", "route").query({ offset: T.number().default(0) })`
// `GET /?offset=30
ctx.validatedQuery.offset; // -> number (30)

// `R.post("/", "route").body({ tags: [T.string()] })`
// Request body is validated as well
ctx.validatedBody.tags; // -> string[]

// `R.get("/", "route").response({ foo: "bar" })`
// Responses are validated as well. This ensures that you as the API creator adhere to the structure (or contract).
ctx.body = {}; // throws a `500 Internal Server Error`, response did not pass validators
ctx.body = { foo: "bar" }; // returns a `200 OK`.
};
```

## Integrate with other Compas features

Compas provides a few features to make working with Koa and Compas a more
integrated experience.

::: details

Under construction

- `getApp` from `@compas/server`
- `createBodyParsers` from `@compas/server`

:::

0 comments on commit f898392

Please sign in to comment.