Vite + Express + Turso + Drizzle ORM
L'approche
vite-expresspermet de créer un monolithe moderne : un seul projet, un seul serveur, mais avec la puissance d'un backend Node.js et la réactivité d'un frontend Vite.
Voici comment structurer proprement un projet Vite + Express + Turso + Drizzle ORM de A à Z.
1. La Structure des Dossiers
La clé de l'organisation avec vite-express est de ne pas séparer le frontend et le backend dans deux racines distinctes.
Le backend devient le "moteur" qui sert le frontend.
Voici l'arborescence idéale :
mon-projet/
├── src/ # Code Frontend (React/Vite)
│ ├── components/ # Composants React
│ ├── App.tsx # Point d'entrée React
│ └── main.tsx # Mount React
├── server/ # Code Backend (Express)
│ ├── db.ts # Configuration Turso + Drizzle
│ ├── schema.ts # Définition des tables (Drizzle)
│ ├── routes.ts # Routes API (Express)
│ └── index.ts # Point d'entrée du serveur
├── drizzle/ # Dossier généré par Drizzle (migrations)
├── .env # Variables d'environnement
├── drizzle.config.ts # Config Drizzle
├── vite.config.ts # Config Vite
└── package.json
2. Initialisation et Installation
Créez le projet Vite standard, puis ajoutez les dépendances backend.
# 1. Créer le projet Vite (React + TS)
npm create vite@latest mon-projet -- --template react-ts
cd mon-projet
# 2. Installer les dépendances Backend + DB
npm install express cors vite-express dotenv
npm install drizzle-orm @libsql/client
# 3. Installer les dépendances de développement (Types + Tools)
npm install -D @types/express @types/cors tsx drizzle-kit
3. Configuration de la Base de Données (Drizzle + Turso)
Nous allons séparer le schéma de la connexion pour plus de clarté.
Fichier : server/schema.ts
C'est ici que vous définissez vos tables.
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
email: text('email').notNull().unique(),
});
Fichier : server/db.ts
Gestion de la connexion à Turso.
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
import * as schema from './schema';
// On vérifie que les variables d'environnement sont là
if (!process.env.TURSO_DATABASE_URL || !process.env.TURSO_AUTH_TOKEN) {
throw new Error("Missing Turso credentials in .env");
}
const client = createClient({
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
export const db = drizzle(client, { schema });
Fichier : .env
À la racine du projet.
TURSO_DATABASE_URL=libsql://votre-db.turso.io
TURSO_AUTH_TOKEN=votre-token-secret
Fichier : drizzle.config.ts
À la racine (pour que la CLI Drizzle fonctionne).
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './server/schema.ts',
out: './drizzle',
dialect: 'sqlite',
driver: 'turso',
dbCredentials: {
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
},
});
4. Configuration du Serveur (Express + Vite)
C'est le cœur du système qui "colle" le backend et le frontend.
Fichier : server/index.ts
Nous utilisons tsx pour exécuter le TypeScript directement.
import express from 'express';
import cors from 'cors';
import { ViteExpress } from 'vite-express';
import * as dotenv from 'dotenv';
// Charger les variables d'environnement
dotenv.config();
const app = express();
// Middlewares
app.use(cors()); // Utile si vous testez l'API depuis l'extérieur
app.use(express.json()); // Pour parser le JSON dans les requêtes POST
// --- Vos Routes API ---
// Exemple simple
app.get('/api/ping', (req, res) => {
res.json({ message: 'pong' });
});
// Importez vos vraies routes ici (ex: import('./routes')...)
// import('./routes').then(route => route.default(app));
// --- Lancement du Serveur ---
const PORT = 3000;
ViteExpress.listen(app, PORT, () =>
console.log(`Serveur lancé sur http://localhost:${PORT}`)
);
Fichier : server/routes.ts (Optionnel, pour la propreté)
import { Router } from 'express';
import { db } from './db';
import { users } from './schema';
const router = Router();
router.get('/users', async (req, res) => {
const allUsers = await db.select().from(users);
res.json(allUsers);
});
router.post('/users', async (req, res) => {
const { name, email } = req.body;
const result = await db.insert(users).values({ name, email }).returning();
res.json(result[0]);
});
export default router;
(N'oubliez pas d'importer ce routeur dans server/index.ts)
5. Configuration de package.json
Il faut configurer les scripts pour gérer la base de données et lancer l'application.
Ajoutez/modifiez la section scripts :
"scripts": {
"dev": "tsx watch server/index.ts",
"build": "tsc && vite build",
"preview": "tsx server/index.ts",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push"
}
Note : tsx watch red émarre automatiquement le serveur backend quand vous modifiez un fichier dans /server. Vite s'occupe déjà du Hot Reload pour le frontend dans /src.
6. Utilisation dans le Frontend
Maintenant, depuis votre React (src/App.tsx), vous pouvez appeler votre API sans problème de CORS ni d'URL complexe, car le backend sert le frontend.
// src/App.tsx
import { useState, useEffect } from 'react'
function App() {
const [users, setUsers] = useState([]);
useEffect(() => {
// On appelle notre propre backend
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []);
return (
<div>
<h1>Liste des utilisateurs</h1>
<ul>
{users.map((u: any) => <li key={u.id}>{u.name}</li>)}
</ul>
</div>
)
}
export default App
Résumé du flux de travail
- Créer la table : Modifiez
server/schema.ts, puis lanceznpm run db:push(pour envoyer le schéma à Turso instantanément). - Coder l'API : Ajoutez une route dans
server/routes.ts. - Coder le Front : Utilisez
fetch('/api/...')danssrc/. - Lancer :
npm run dev.
Hébergement
Nous avons créé notre application Web "To-Do List" avec :
- Vite (React)
- Express (Backend)
- Drizzle ORM
- Turso
Pour héberger notre application Web, nous pouvons utiliser Render (gratuit, simple, supporte Node.js natif).
Render offre un tier gratuit très généreux pour les "Web Services".
Avantages pour notre projet :
- Gratuit (avec quelques limitations : le serveur "s'endort" après 15 min d'inactivité, il faut ~1 min pour le réveiller).
- Compatible nativement avec Node.js.
- HTTPS automatique.
- Très facile à connecter à GitHub (déploiement automatique à chaque git push).