API Routes

Learn how to create and manage API routes in your Remix Firebase application.

Makerkit provides some utilities to reduce the boilerplate needed to write Remix API functions. This section will teach you everything you need to know to write your API functions.

Calling API functions from the client

To make requests to the API functions in the api folder, we provide a custom hook useApiRequest, a wrapper around fetch.

It has the following functionality:

  1. Uses an internal state to keep track of the state of the request, like loading, error and success. This helps you write fetch requests the "hooks way"
  2. Automatically adds a Firebase AppCheck Token to the request headers if you enabled Firebase AppCheck
  3. Automatically adds a CSRF token to the request headers

Similarly to making Firestore Requests, it's a convention to create a custom hook for each request for readability/reusability reasons.

src/core/hooks/use-create-session.ts
import useApiRequest from '~/core/hooks/use-api'; interface Body { idToken: string; } export function useCreateSession() { return useApiRequest<void, Body>('/api/session/sign-in'); }

The hook returns an array:

  1. Callback: the first element is the callback to make the request
  2. State: the second element is the state object of the request
const [createSession, createSessionState] = useCreateSession();

The state object internally uses useApiRequest, and has the following interface:

{ success: boolean; loading: boolean; error: string; data: T | undefined; }

When success is true, the property data is inferred with its correct type.

You can use this hook in your components:

import { useCreateSession } from '~/core/hooks/use-create-session'; function Component() { const [createSession, createSessionState] = useCreateSession(); return ( <> { createSessionState.loading ? `Loading...` : null } { createSessionState.error ? `Error :(` : null } { createSessionState.success ? `Yay, success!` : null } <SignInForm onSignIn={(idToken) => createSession({ idToken })} /> </> ); }

Similarly, you can also use it to fetch data:

import { useCreateSession } from '~/core/hooks/use-create-session'; function Component() { const [fetchMembers, { loading, error, data }] = useFetchMembers(); // fetch data when the component mounts useEffect(() => { fetchMembers(); }, [fetchMembers]); if (loading) { return <div>Fetching members...</div>; } if (error) { return <div>{error}</div>; } return ( <div> {data.map(member => <MemberItem member={member} />)} </div> ); }

Writing your own Fetch Hook

You are free to use your own implementation for sending HTTP requests to your API or use a powerful third-party library.

With that said, you need to ensure that you're sending the required headers:

  1. The AppCheck token (if you use Firebase AppCheck)
  2. The CSRF Token (for POST requests)
Generating an App Check Token

To generate an App Check token, you can use the useGetAppCheckToken hook:

const getAppCheckToken = useGetAppCheckToken(); const appCheckToken = await getAppCheckToken(); console.log(appCheckToken) // token

You will need to send a header X-Firebase-AppCheck with the resolved value returned by the promise getAppCheckToken().

Sending the CSRF Token

To retrieve the page's CSRF token, you can use the useGetCsrfToken hook:

const getCsrfToken = useGetCsrfToken(); const csrfToken = getCsrfToken(); console.log(csrfToken) // token

You will need to send a header x-csrf-token with the value returned by getCsrfToken().

CSRF Token check

We must pass a CSRF token when using POST requests to prevent malicious attacks. To do so, we use the pipe withCsrfToken.

  1. The CSRF token is generated when the page is server-rendered
  2. The CSRF token is stored in a meta tag
  3. The CSRF token is sent to an HTTP POST request automatically when using the useApiRequest hook
  4. This function will throw an error when the token is invalid

By using this function, we ensure all the following functions will not be executed unless the token is valid.

export const action: ActionFunction = async ({ request }) => { await withCsrf(request); }

Firebase App Check

App Check is a Firebase service that helps you to protect your web app from bots, spammy users, and general abuse detected by Google's Recaptcha.

Check out the documentation to setup Firebase App Check.

Using the withAppCheck pipe, we ensure all the following functions will not be executed unless the App Check token is valid.

export const action: ActionFunction = async ({ request }) => { await withAppCheck(request); }

Catching and Handling Exceptions

To catch and gracefully handle API exceptions, we use the function withExceptionFilter.

As seen from its usage above, we wrap the API function within the utility. When errors are caught, this function will:

  1. Log and debug the error to the console
  2. Report the error to Sentry (if configured)
  3. Return an object stripping the error's data to avoid leaking information

API Logging

The boilerplate uses Pino for API logging, a lightweight logging utility for Node.js.

Logging is necessary to debug your applications and understand what happens when things don't behave as expected. You will find various instances of logging throughout the API, but we encourage you to log more if necessary.

We import the logging utility from ~/core/logger. Typically, you can log every time you perform an action, both before and after. For example:

async function myFunction(params: { organizationId: string; userId: string; }) { logger.info( { organizationId: params.organizationId, userId: params.userId, }, `Performing action...` ); await performAction(); logger.info( { organizationId: params.organizationId, userId: params.userId, }, `Action successful` ); }

It's always important to add context to your logs: as you can see, we use the first parameter to add important information.


Subscribe to our Newsletter
Get the latest updates about React, Remix, Next.js, Firebase, Supabase and Tailwind CSS