> ## Content Index
> Fetch the complete content index at: https://catalins.tech/llms.txt
> Use this file to discover other available public pages before exploring further.

# Better Auth with Hono, Bun, TypeScript, React and Vite
- URL: https://catalins.tech/better-auth-with-hono-bun-typescript-react-vite/
- Published: 2025-04-13T07:29:20.000Z
- Updated: 2025-04-13T07:30:19.000Z
- Author: Catalin Pit
- Tags: Auth, Hono, Bun, TypeScript, React, Vite, Better Auth

You'll learn how to implement authentication with [Better Auth](http://better-auth.com/) in a client - server architecture, where the frontend is separate from the backend. 

The backend uses [Hono](http://hono.dev/) and [Bun](https://bun.sh/), whereas the frontend uses [React](https://react.dev/) with [Vite](https://vite.dev/). For brevity, this article will only include the relevant bits instead of taking you through the whole process of building the full-stack application from scratch.  
  
You can find the link to the repository with the complete code at the end of this article.

## Backend: Better Auth with Hono and Bun

We'll start by installing Better Auth:

```
bun add better-auth

```

After that, create an `.env` file with the following environment variables:

```
# Auth
BETTER_AUTH_SECRET=<generate-a-secret-key>
BETTER_AUTH_URL=<url-of-your-server> (e.g. http://localhost:1234)
```

Now you're ready to configure Better Auth.

### Create the Better Auth Instance

In my case, I placed the Better Auth instance in the [auth.ts](https://github.com/catalinpit/example-app/blob/main/server/lib/auth.ts) file. My project has the following requirements:

- it uses PostgreSQL for the database
- it uses Prisma ORM
- the app should allow users to log in/sign up with email and password
- the app should allow users to log in/sign up with their Google or GitHub accounts
- users can have different roles

```ts
import { Role } from "@prisma/client";
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";

import prisma from "@/db/index";
import env from "@/env";

export const auth = betterAuth({
  database: prismaAdapter(prisma, {
    provider: "postgresql",
  }),
  // Allow requests from the frontend development server
  trustedOrigins: ["http://localhost:5173"],
  emailAndPassword: {
    enabled: true,
  },
  socialProviders: {
    github: {
      clientId: env.GITHUB_CLIENT_ID,
      clientSecret: env.GITHUB_CLIENT_SECRET,
    },
    google: {
      clientId: env.GOOGLE_CLIENT_ID,
      clientSecret: env.GOOGLE_CLIENT_SECRET,
    },
  },
  user: {
    additionalFields: {
      roles: {
        type: [Role.STUDENT, Role.CREATOR, Role.ADMIN],
        required: true,
        defaultValue: [Role.STUDENT],
      },
    },
  },
});
```

The above code does the following:

1. It sets up the database to use Prisma ORM and PostgreSQL.
2. It specifies the trusted origins, which are the origins allowed to make requests.
  1. In this case, it's the `client` (frontend). All the other origins that are not specified are automatically blocked.
3. It enables email/password authentication and configures social login providers.
4. Lastly, it extends the core `user` schema to add the additional field `roles`
  1. That means that each user can have multiple roles.

Once you're done with configuring your Better Auth instance, run the following command to generate the ORM schema:

```
bunx @better-auth/cli generate
```

It adds all the required models, fields, and relationships to the Prisma schema file.

### Create the API Handler

The next step is to set up a route handler for the auth API requests.

The route below uses the handler provided by Better Auth to serve all `POST` and `GET` requests to the `/api/auth` endpoint.

```ts
import { auth } from "@/lib/auth";
import { createRouter } from "@/lib/create-app";

const router = createRouter();

router.on(["POST", "GET"], "/auth/**", (c) => {
  return auth.handler(c.req.raw);
});

export default router;
```

The above code lives in the [routes/auth.ts](https://github.com/catalinpit/example-app/blob/main/server/routes/auth.ts) file.

Lastly, you need to mount the route in the [app.ts](https://github.com/catalinpit/example-app/blob/main/server/src/app.ts) file.

```ts
....
import createApp from "@/lib/create-app";
import auth from "@/routes/auth";
....

const app = createApp();

const routes = [userSettings, creator, student, courses, auth] as const;

routes.forEach((route) => {
  app.basePath("/api").route("/", route);
});

....

export default app;
```

Once that's done, Better Auth on the backend is all set. 

## Frontend: Better Auth with React and Vite

The first step is to install Better Auth in the client directory:

```
bun add better-auth
```

Once that's installed, navigate to [/src/lib/auth-client](https://github.com/catalinpit/example-app/blob/main/client/src/lib/auth-client.ts) and add the following code:

```ts
import { createAuthClient } from "better-auth/react";
import { inferAdditionalFields } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [
    inferAdditionalFields({
      user: {
        roles: {
          type: "string[]",
        },
      },
    }),
  ],
});

export const {
  useSession,
  signIn,
  signUp,
  signOut,
  forgetPassword,
  resetPassword,
} = authClient;

export type Session = typeof authClient.$Infer.Session;
export type User = typeof authClient.$Infer.Session.user;
```

The client-side library allows you to interact with the Better Auth instance from the server.

The above code:

- Calls the `createAuthClient` function to create the auth client.
- Stores the result of the function call in a constant `authClient`.
  - It returns various hooks and functions related to authentication.
- Uses the `plugins` property to extend the user object by adding the `roles` property.
- Exports specific auth hooks and methods, so you don't have to import the entire `authClient` object to use them.
  - You can import and use them directly.
- Lastly, it uses the `$Infer` property from Better Auth to export the "User" and "Sessions" types.

You're done and can start authenticating users.

## Register Component

You need to import the `signUp` object from the [/src/lib/auth-client](https://github.com/catalinpit/example-app/blob/main/client/src/lib/auth-client.ts) file and pass the data provided by the user to its `email` method.

The method provides various callbacks, such as `onRequest`, `onSuccess`, and `onError`, allowing you to perform various actions at different stages of the registration process.

For example, the `onRequest` callback could be used to set a loading state until the registration resolves.

In this example, the user is redirected to the homepage if the registration is successful *(redundant since that's the default Better-Auth behaviour, but its purpose is to showcase the onSuccess callback)*. 

If there is an error during the registration process, it'll log the error *(not recommended for production use)*.

```ts
...
  
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    if (password !== confirmPassword) {
      alert("Passwords do not match");
      return;
    }

    await signUp.email(
      { email, password, name, roles: ["STUDENT"] },
      {
        onSuccess: () => {
          navigate({ to: "/" });
        },
        onError: (error) => {
          console.error(error);
        },
      }
    );
  };

...
```

Better Auth automatically redirects the user to the home page on successful registration. If you want to change the default behaviour, see how to do it [here](https://www.better-auth.com/docs/basic-usage#sign-up:~:text=By%20default%2C%20the%20users%20are%20automatically%20signed%20in%20after%20they%20successfully%20sign%20up.%20To%20disable%20this%20behavior%20you%20can%20set%20autoSignIn%20to%20false.).

## Login Component

The login functionality works similarly to the registration functionality.

Import the `signIn` object from the auth-client and call the appropriate authentication methods. In this case:

- `email` to allow the users to sign in with email and password
- `social` to allow the users to sign in via Google or GitHub

```ts
...

  const handleEmailLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    await signIn.email(
      { email, password },
      {
        onSuccess: () => {
          navigate({ to: "/" });
        },
        onError: (error) => {
          console.error(error);
        },
      }
    );
  };

  const handleGoogleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    await signIn.social(
      { provider: "google" },
      {
        onSuccess: () => {
          navigate({ to: "/" });
        },
        onError: (error) => {
          console.error(error);
        },
      }
    );
  };

  const handleGithubLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    await signIn.social(
      { provider: "github" },
      {
        onSuccess: () => {
          navigate({ to: "/" });
        },
        onError: (error) => {
          console.error(error);
        },
      }
    );
  };

  ...
```

## Demo

The video below shows the following flows:

- registers a user
- signs out the user
- signs in the user

0:00 

/0:19 

1× 

> The complete code is available in this [repository](https://github.com/catalinpit/example-app/).

## What's next?

- Implement Resend to send emails
- Implement Polar to accept payments

## References:

- [Installation guide](https://www.better-auth.com/docs/installation)
- [Prisma adapter](https://www.better-auth.com/docs/concepts/database#prisma-adapter)
- [Trusted origins](https://www.better-auth.com/docs/reference/security#trusted-origins)
- [Social login providers (OAuth)](https://www.better-auth.com/docs/concepts/oauth)
- [Extending core schema](https://www.better-auth.com/docs/concepts/database#extending-core-schema)
- [Additional fields](https://www.better-auth.com/docs/concepts/typescript#additional-fields)
- [Inferring Types](https://www.better-auth.com/docs/concepts/typescript)