Building a full-stack application with Next.js and AdonisJS using TypeScript ensures type safety across your project, making your code more maintainable and reducing potential errors. This guide will show you how to fully leverage TypeScript in both the front-end and back-end and provide detailed steps to set up authentication for secure, real-world applications.
Why Choose Next.js and AdonisJS with TypeScript?
- Next.js: A React-based framework offering server-side rendering, static site generation, and built-in TypeScript support.
- AdonisJS: A TypeScript-first back-end framework inspired by Laravel, providing routing, ORM, and authentication out of the box.
- TypeScript: Provides strong typing and tooling for a seamless developer experience.
Set Up the AdonisJS Back-End with TypeScript
AdonisJS natively supports TypeScript, making it a great choice for modern back-end development.
- Step 1: Install AdonisJS CLI and Create a Project
Install the CLI globally:
1
npm install -g @adonisjs/cli
Create a new project using the API boilerplate:
1 2
adonis new backend-app --typescript cd backend-app
- Step 2: Configure the Database
Update the .env file with your database credentials:
1 2 3 4 5 6
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_USER=root DB_PASSWORD=yourpassword DB_DATABASE=adonis
Run the migrations to set up the database schema:
1
node ace migration:run
- Step 3: Test the Server
Start the development server:
1
npm run dev
The server will run at http://localhost:3333
.
Set Up Authentication in AdonisJS
Authentication is a key part of any application. AdonisJS provides a flexible authentication system that supports JWT, session-based authentication, and API tokens.
- Step 1: Install and Configure Authentication
Install the authentication package:
1 2
npm install @adonisjs/auth node ace configure @adonisjs/auth
Choose "API Tokens" for a typical full-stack app. This will update the project with:
User model and migration.
1
Auth config file (config/auth.ts).
Run the migration to create the users table:
1
node ace migration:run
- Step 2: Create Auth Routes
Add routes for user registration, login, and fetching user details in start/routes.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
import Route from '@ioc:Adonis/Core/Route'; import Hash from '@ioc:Adonis/Core/Hash'; import User from 'App/Models/User'; Route.post('/register', async ({ request }) => { const data = request.only(['email', 'password']); const user = await User.create(data); return user; }); Route.post('/login', async ({ auth, request, response }) => { const { email, password } = request.only(['email', 'password']); const user = await User.findByOrFail('email', email); if (!(await Hash.verify(user.password, password))) { return response.unauthorized('Invalid credentials'); } const token = await auth.use('api').generate(user); return { token }; }); Route.get('/me', async ({ auth }) => { return auth.user; }).middleware('auth');
- Step 3: Test the API
Use a tool like Postman or curl to test the API endpoints:
1 2 3
Register: POST /register with { "email": "user@example.com", "password": "secret" }. Login: POST /login with { "email": "user@example.com", "password": "secret" }. Get User Info: GET /me with the Authorization: Bearer header.
Set Up the Next.js Front-End with TypeScript
Next.js has built-in TypeScript support.
- Step 1: Create a TypeScript Project
1 2
npx create-next-app frontend-app --typescript cd frontend-app
- Step 2: Install Axios
Install Axios for API interactions:
1
npm install axios
- Step 3: Configure API Client
Create a reusable Axios instance with TypeScript support in utils/api.ts
:
1 2 3 4 5 6 7
import axios from 'axios'; const api = axios.create({ baseURL: 'http://localhost:3333', // AdonisJS server URL }); export default api;
- Step 4: Add Type Definitions
Create type definitions for user and authentication responses in types/index.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13
export interface User { id: number; email: string; } export interface LoginResponse { token: string; } export interface RegisterResponse { id: number; email: string; }
- Implement Authentication in Next.js
- Step 1: Create Auth Context
Create an authentication context to manage user state and tokens in contexts/AuthContext.tsx
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
import { createContext, useState, useEffect, ReactNode } from 'react'; import api from '../utils/api'; import { User, LoginResponse } from '../types'; interface AuthContextProps { user: User | null; login: (email: string, password: string) => Promise; logout: () => void; } export const AuthContext = createContext<AuthContextProps | null>(null); export const AuthProvider = ({ children }: { children: ReactNode }) => { const [user, setUser] = useState<User | null>(null); const login = async (email: string, password: string) => { const response = await api.post('/login', { email, password }); localStorage.setItem('token', response.data.token); const userResponse = await api.get('/me', { headers: { Authorization: Bearer ${response.data.token} }, }); setUser(userResponse.data); }; const logout = () => { localStorage.removeItem('token'); setUser(null); }; useEffect(() => { const token = localStorage.getItem('token'); if (token) { api.get('/me', { headers: { Authorization: Bearer ${token} } }) .then((response) => setUser(response.data)) .catch(() => logout()); } }, []); return ( <AuthContext.Provider value={{ user, login, logout }}> {children} </AuthContext.Provider> ); };
- Step 2: Use Auth Context in Components
Wrap your application with the AuthProvider in pages/_app.tsx
:
1 2 3 4 5 6 7 8 9 10
import '../styles/globals.css'; import { AuthProvider } from '../contexts/AuthContext'; function MyApp({ Component, pageProps }: any) { return ( <Component {...pageProps} /> ); } export default MyApp;
Use the context in pages like pages/login.tsx
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
import { useContext, useState } from 'react'; import { AuthContext } from '../contexts/AuthContext'; export default function LoginPage() { const auth = useContext(AuthContext); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleLogin = async () => { try { await auth?.login(email, password); alert('Logged in successfully!'); } catch { alert('Login failed'); } }; return ( <div> <h1>Login</h1> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} /> <Button onClick={handleLogin}>Login</Button> <div> ); }
Conclusion
By fully utilizing TypeScript, Next.js, and AdonisJS, you’ve built a secure and scalable full-stack application. The strong typing ensures better maintainability and fewer runtime errors, while AdonisJS’s built-in authentication and Next.js’s flexibility make this stack ideal for modern web apps.
If you’d like to expand this project, consider:
- Adding role-based access control.
- Using a library like React Query for server state management.
- Deploying the application to production with services like Vercel and Heroku.
Let me know in the comments if you’d like to explore these advanced topics!