Introduction

The frontend uses the latest version of Next.js and TypeScript; it is configured using the latest app router.

You can run the frontend only by running the pnpm run dev command from a terminal within the web directory. Alternatively, you can run the entire application by running pnpm run dev from the root directory.

App structure

The app root directory is structured as follows:

web
├── app
├── components
├── lib
├── public
├── .env
├── .env.example
├── .eslintrc.js
├── components.json
├── next.config.js
├── package.json
├── postcss.config.js
├── README.md
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock

App

ItemDescription
globals.cssTailwind global config. Uses a generated theme from ShadCN UI themes
layout.tsxApp layout
page.tsxLanding page

Components

ItemDescription
layoutsThe dashboard layout is stored which wraps the application {children} in root/app/layout.tsx
themeThe ThemeProvider component(s) that wrap the application.
uiWhere ShadCN components are stored upon being generated. Configured by the components.json located in the frontend root
*.tsxComponents used within the application

Lib

ItemDescription
apiThe output generated by the openapi npm package. Configured in the package.json task named generate-client
configStores site config. Currently contains the setup for sidebar navigation
twConfig.tsTailwind colour config exported to an accessible Object. Used for assigning theme-aware colours in charts
utils.tsShadCN utils

TypeScript from FastAPI

As FastAPI is based on the OpenAPI specification, you get automatic compatibility with many tools, including the automatic API docs (provided by Swagger UI).

One particular advantage that is not necessarily obvious is that you can generate clients (sometimes called SDKs ) for your API, for many different programming languages.

For a more detailed explanation, see the official FastAPI documentation

Why generate TypeScript?

Generating a TypeScript client for your FastAPI backend is a great way to ensure that your frontend and backend are always in sync. It is also a way to provide type hinting while writing your frontend code without needing a permanent reference to the API, or re-creating the schema using something like Zod.

Type hinting from the generated API client

How to generate TypeScript

1

Configure and run generate-client task

There are two generate-client tasks configured in the package.json:

"scripts": {
  ...
  "generate-client": "openapi --input https://next-fast-turbo.vercel.app/openapi.json --output ./lib/api/client --client axios --useOptions --useUnionTypes",
  "generate-client:dev": "openapi --input http://127.0.0.0:8000/openapi.json --output ./lib/api/client --client axios --useOptions --useUnionTypes"
  ...
},

The generate-client task is set to run off the production OpenAPI schema.

The generate-client:dev task is set to use the localhost OpenAPI schema. This is useful for development, as it will use the latest schema from the backend.

Ensure your production API URL is configured, or that your local API URL is correct, and then run the relevant task.

2

Update your root layout

A key file to be aware of is the OpenAPI.ts which is generated in the lib/api/client/core directory. This file has the main configuration for the API connection as below:

lib/api/client/core/OpenAPI.ts
export const OpenAPI: OpenAPIConfig = {
  BASE: "http://127.0.0.0:8000",
  VERSION: "0.1.0",
  WITH_CREDENTIALS: false,
  CREDENTIALS: "include",
  TOKEN: undefined,
  USERNAME: undefined,
  PASSWORD: undefined,
  HEADERS: undefined,
  ENCODE_PATH: undefined,
};

The problem with this is that we would want to differentiate between which API is being used. When the TypeScript is generated, the given OpenAPI URL is used as the base URL for the API. This is not ideal for production, as you would want to use the production API URL, and vice versa in development.

You can override this by adding the following to your root layout.tsx file:

layout.tsx
import { OpenAPI } from "@/lib/api/client";

if (process.env.NODE_ENV === "production") {
  OpenAPI.BASE = "https://next-fast-turbo.vercel.app"
}

Using the generated API

Once you have generated your TypeScript interface to your API, you can use it in your frontend code.

In its simplest form, you can import the generated API and use it as below:

import { UsersService } from "@/lib/api/client";

const response = await UsersService.usersSearchUsers({
        keyword: "keyword",
        searchOn: "searchOn",
        maxResults: "maxResults",
      });

You can see this code being used in the components/search-users.tsx file, as part of the form’s onSubmit function:

  const onSubmit = async (data: z.infer<typeof FormSchema>) => {
    try {
      const maxResults = data.searchResults ? parseInt(data.searchResults) : 10;
      const response = await UsersService.usersSearchUsers({
        keyword: data.keyword,
        searchOn: data.searchOn,
        maxResults: maxResults,
      });
      setSearchResults(response);
      setError(null);
    } catch (error) {
      setSearchResults({ results: [] });
      setError(error);
    }
  };

This code also imports the UserSearchResults type which is used to set the state of the search results:

import { UsersService, UserSearchResults } from "@/lib/api/client";

const [searchResults, setSearchResults] = React.useState<UserSearchResults>({
    results: [],
  });

Important considerations

  • If you make changes to your API, you will need to re-generate the TypeScript interface.
  • Re-generating this interface will overwrite the existing files. If you want to modify anything (e.g. api/client/core/OpenAPI.ts) and have it persist, do this outside of the generated files. You can see this in action in the root/app/layout.tsx file.