La autenticación es la piedra angular de cualquier aplicación web moderna, sin embargo, configurarla sigue siendo uno de los desafíos que más tiempo consume para los desarrolladores. Presentamos Better Auth API—una solución de autenticación agnóstica al framework que promete transformar la forma en que implementamos la gestión de usuarios. En esta guía completa, recorreremos la construcción de una aplicación full-stack completa que demuestra el poder y la simplicidad de Better Auth, utilizando Bun para un rendimiento ultrarrápido.
¿Quieres una plataforma integrada y todo en uno para que tu equipo de desarrolladores trabaje en conjunto con máxima productividad?
¡Apidog satisface todas tus demandas y reemplaza a Postman a un precio mucho más asequible!
¿Qué es Better Auth API?
Better Auth API es un framework de autenticación moderno y de código abierto diseñado para funcionar sin problemas en cualquier entorno JavaScript. A diferencia de las bibliotecas de autenticación tradicionales que te encierran en ecosistemas específicos, Better Auth proporciona una API unificada que se adapta a tu pila, ya sea que uses React, Vue, Svelte o JavaScript puro en el frontend, y Node.js, Bun o Deno en el backend. Su arquitectura basada en plugins admite múltiples estrategias de autenticación, desde correo electrónico/contraseña tradicional hasta proveedores OAuth, passkeys y enlaces mágicos, todo ello manteniendo la seguridad de tipos y una excelente experiencia de desarrollador.

Primeros pasos con Better Auth: Requisitos previos y configuración del proyecto
Antes de sumergirnos en el código, asegúrate de tener lo siguiente instalado:
- Bun 1.0+: Usaremos Bun a lo largo de este tutorial por su velocidad excepcional y herramientas modernas.
- Node.js 18+: Requerido para el entorno de ejecución del backend.
- Un editor de código: VS Code recomendado para soporte de TypeScript.

Si bien esta configuración funciona perfectamente con npm, demostraremos el flujo de trabajo de Bun, que ofrece una instalación de paquetes 3-5 veces más rápida y una experiencia de desarrollo más optimizada.
Construyendo un proyecto de ejemplo: Implementación paso a paso
Creemos un sistema de autenticación práctico con un frontend de React y un backend de Express, completo con persistencia en la base de datos.
Paso 1: Configuración del Backend con Express y Drizzle ORM
1. Inicializar el Proyecto Backend
Primero, crea y entra en tu directorio backend:
mkdir better-auth-backend
cd better-auth-backend
bun init -y
2. Instalar Dependencias
Necesitaremos Express para el servidor, Better Auth para la autenticación y Drizzle ORM para la gestión de la base de datos:
bun add express better-auth drizzle-orm
bun add -D @types/bun @types/express drizzle-kit
3. Configurar Variables de Entorno
Crea un archivo .env para almacenar la configuración sensible:
BETTER_AUTH_SECRET=your-secret-key-here # Generar con: openssl rand -base64 32
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL=local.db
El BETTER_AUTH_SECRET también se puede generar en el sitio web de better-auth.
4. Crear Esquema de Base de Datos con Drizzle ORM
Better Auth funciona mejor con Drizzle, que proporciona un excelente soporte de TypeScript y evita problemas de módulos nativos. Crea src/db/schema.ts:
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
export const user = sqliteTable("user", {
id: text("id").primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
emailVerified: integer('email_verified', { mode: 'boolean' }).notNull().default(false),
image: text('image'),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
});
export const session = sqliteTable("session", {
id: text("id").primaryKey(),
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
token: text('token').notNull().unique(),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
userId: text('user_id').notNull(),
});
export const account = sqliteTable("account", {
id: text("id").primaryKey(),
accountId: text('account_id').notNull(),
providerId: text('provider_id').notNull(),
userId: text('user_id').notNull(),
accessToken: text('access_token'),
refreshToken: text('refresh_token'),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
});
export const verification = sqliteTable("verification", {
id: text("id").primaryKey(),
identifier: text('identifier').notNull(),
value: text('value').notNull(),
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
});
5. Configurar Conexión a la Base de Datos
Crea src/db/index.ts usando el binding nativo de SQLite de Bun:
import { Database } from "bun:sqlite";
import { drizzle } from "drizzle-orm/bun-sqlite";
const sqlite = new Database("local.db");
export const db = drizzle(sqlite);
6. Configurar Better Auth
Crea src/lib/auth.ts para configurar la API de Better Auth:
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "../db";
import { user, session, account, verification } from "../db/schema";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "sqlite",
schema: {
user: user,
session: session,
account: account,
verification: verification,
},
}),
emailAndPassword: {
enabled: true,
},
trustedOrigins: ["http://localhost:5173"],
});
7. Crear Servidor Express
En src/index.ts, monta el manejador de la API de Better Auth:
import express from "express";
import cors from "cors";
import { toNodeHandler } from "better-auth/node";
import { auth } from "./lib/auth";
const app = express();
const PORT = process.env.PORT || 3000;
app.use(
cors({
origin: "http://localhost:5173",
credentials: true,
})
);
// Monta la API de Better Auth en /api/auth
app.use("/api/auth", toNodeHandler(auth));
app.use(express.json());
app.get("/api/me", async (req, res) => {
const session = await auth.api.getSession({
headers: req.headers,
});
if (!session) {
return res.status(401).json({ error: "Unauthorized" });
}
res.json({ user: session.user });
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
8. Ejecutar Migración de Base de Datos
Crea drizzle.config.ts en la raíz del backend:
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "sqlite",
schema: "./src/db/schema.ts",
out: "./drizzle",
dbCredentials: {
url: "local.db",
},
});
Ejecuta la migración para crear las tablas:
bunx drizzle-kit push

Paso 2: Configuración del Frontend con React y Vite
1. Crear Aplicación React
En una nueva terminal, inicializa el frontend:
bun create vite better-auth-frontend --template react-ts
cd better-auth-frontend
2. Instalar Dependencias
bun add better-auth
3. Configurar Tailwind CSS (Actualización V4)
Post-Tailwind CSS v4 requiere una configuración diferente. Instala los nuevos paquetes:
bun add -D tailwindcss postcss @tailwindcss/postcss
Crea tailwind.config.js en la raíz del proyecto:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Crea postcss.config.js:
export default {
plugins: {
"@tailwindcss/postcss": {},
},
}
Crea src/index.css:
@import "tailwindcss";
body {
margin: 0;
font-family: system-ui, -apple-system, sans-serif;
}
4. Configurar Cliente Better Auth
Crea src/lib/auth-client.ts:
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
});
export const { signIn, signUp, useSession } = authClient;
5. Construir la Interfaz de Usuario de Autenticación
Reemplaza src/App.tsx con una interfaz de autenticación completa:
import { useState } from 'react';
import { useSession, signIn, signUp } from './lib/auth-client';
function App() {
const { data: session, isPending } = useSession();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [name, setName] = useState('');
const handleSignUp = async (e: React.FormEvent) => {
e.preventDefault();
await signUp.email({
name,
email,
password,
callbackURL: '/',
});
};
const handleSignIn = async (e: React.FormEvent) => {
e.preventDefault();
await signIn.email({
email,
password,
});
};
const handleSignOut = async () => {
await authClient.signOut();
};
if (isPending) return <div className="flex items-center justify-center min-h-screen">Cargando...</div>;
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center p-4">
<div className="max-w-md w-full bg-white rounded-lg shadow-md p-6">
<h1 className="text-2xl font-bold text-center mb-6 text-gray-800">
Prueba de Better Auth API
</h1>
{session?.user ? (
<div className="space-y-4">
<div className="bg-green-50 p-4 rounded-md">
<p className="text-green-800 font-semibold">Sesión iniciada como:</p>
<p className="text-green-700">{session.user.email}</p>
<p className="text-green-600 text-sm">{session.user.name}</p>
</div>
<button
onClick={handleSignOut}
className="w-full bg-red-500 hover:bg-red-600 text-white font-medium py-2 px-4 rounded-md transition"
>
Cerrar Sesión
</button>
</div>
) : (
<div className="space-y-4">
<form onSubmit={handleSignUp} className="space-y-3">
<h2 className="text-lg font-semibold text-gray-700">Registrarse</h2>
<input
type="text"
placeholder="Nombre"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
<input
type="email"
placeholder="Correo electrónico"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
<input
type="password"
placeholder="Contraseña"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
<button
type="submit"
className="w-full bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4 rounded-md transition"
>
Crear Cuenta
</button>
</form>
<form onSubmit={handleSignIn} className="space-y-3">
<h2 className="text-lg font-semibold text-gray-700">Iniciar Sesión</h2>
<input
type="email"
placeholder="Correo electrónico"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
<input
type="password"
placeholder="Contraseña"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
<button
type="submit"
className="w-full bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-md transition"
>
Iniciar Sesión
</button>
</form>
</div>
)}
</div>
</div>
);
}
export default App;

Paso 3: Probando la Integración
1. Iniciar Servidor Backend
cd better-auth-backend
bun dev

2. Iniciar Desarrollo Frontend
cd better-auth-frontend
bun dev

3. Probar el Flujo de Autenticación
- Abre
http://localhost:5173en tu navegador - Registra un nuevo usuario usando el formulario de registro (Sign-Up)


- Observa cómo el archivo de la base de datos crece a medida que se llenan las tablas
- Cierra sesión y vuelve a iniciarla para verificar la gestión de sesiones
- Verifica el endpoint de sesión:
http://localhost:3000/api/auth/session

Beneficios clave de Better Auth API
La Better Auth API se distingue por varias ventajas convincentes:
- Agnosticismo de Framework: A diferencia de NextAuth.js o Firebase Auth, Better Auth funciona dondequiera que se ejecute JavaScript. La misma lógica de autenticación sirve para clientes web, móviles y API sin modificaciones.
- Múltiples Estrategias de Autenticación: Soporte listo para usar para credenciales, OAuth 2.0, passkeys, autenticación de dos factores y enlaces mágicos. Cada estrategia es un plugin que se puede habilitar con una sola línea de configuración.
- Seguridad de Tipos: Soporte completo de TypeScript con tipos inferidos de tu esquema de base de datos elimina el "infierno de tipos any" común en las bases de código de autenticación.
- Flexibilidad de Base de Datos: El adaptador Drizzle ORM significa que puedes cambiar entre SQLite, PostgreSQL y MySQL sin cambiar tu lógica de autenticación. Este tutorial usa SQLite por simplicidad, pero el mismo código escala a bases de datos de producción.
- Ecosistema de Plugins: ¿Necesitas soporte para organizaciones? ¿Multi-tenencia? ¿Roles de administrador? El sistema de plugins de Better Auth te permite extender la funcionalidad sin inflar el núcleo.
- Rendimiento: Con Bun como entorno de ejecución, los arranques en frío son de menos de 100 ms, y todo el flujo de autenticación se completa en menos de 50 ms en hardware modesto.
Preguntas Frecuentes
P1: ¿Puedo usar Better Auth API con npm en lugar de Bun?
R: Absolutamente. Si bien esta guía utiliza Bun por sus beneficios de rendimiento, cada comando tiene un equivalente en npm. Reemplaza bun add por npm install, bun dev por npm run dev y bunx por npx. El único código específico de Bun es la importación bun:sqlite, que se puede reemplazar por better-sqlite3 para entornos Node.js.
P2: ¿Por qué necesitamos Drizzle ORM? ¿Better Auth no puede crear tablas automáticamente?
R: Better Auth sigue el principio de gestión explícita de la base de datos. Drizzle proporciona migraciones seguras de tipos, versionado de esquemas y previene la pérdida accidental de datos. El comando drizzle-kit push es una configuración única que te da control total sobre la evolución de tu base de datos.
P3: ¿Qué hago si encuentro el error "Missing parameter name"?
R: Esto ocurre al usar app.all() con comodines en Express. La solución es usar app.use("/api/auth", toNodeHandler(auth)) en su lugar. El manejador de Better Auth gestiona todas las subrutas internamente, por lo que Express no necesita la coincidencia de comodines.
P4: ¿Cómo agrego proveedores de autenticación social?
R: Habilita los plugins OAuth en tu configuración de Better Auth. Por ejemplo, para agregar GitHub:
import { betterAuth } from "better-auth";
import { github } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
github({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
})
]
});
P5: ¿Better Auth API está lista para producción?
R: Sí. Better Auth impulsa la autenticación para varios productos SaaS con miles de usuarios. El framework implementa gestión segura de sesiones, protección CSRF y sigue las directrices de OWASP. Sin embargo, siempre audita tu implementación específica y mantén las dependencias actualizadas.
Conclusión
Construir la autenticación desde cero ya no es necesario con soluciones modernas como la Better Auth API. ¡En esta guía, hemos creado un sistema de autenticación completo, desde el esquema de la base de datos hasta los componentes de la interfaz de usuario, en pocos minutos! La combinación de la flexibilidad de Better Auth, la seguridad de tipos de Drizzle ORM y el rendimiento de Bun crea una experiencia de desarrollador que escala desde el prototipo hasta la producción.
El proceso paso a paso demuestra que la autenticación, aunque crítica, no tiene por qué ser compleja. Al aprovechar la arquitectura de plugins de Better Auth y su diseño agnóstico al framework, puedes concentrarte en construir características que importan a tus usuarios en lugar de luchar con las implementaciones de seguridad.
Ya sea que estés construyendo un proyecto personal o una aplicación empresarial, Better Auth API proporciona la base para una autenticación segura y escalable que se adapta a tus necesidades, y no al revés.
¿Quieres una plataforma integrada y todo en uno para que tu equipo de desarrolladores trabaje en conjunto con máxima productividad?
¡Apidog satisface todas tus demandas y reemplaza a Postman a un precio mucho más asequible!
