Aller au contenu principal

Vite + Express + Turso + Drizzle ORM

L'approche vite-express permet 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

  1. Créer la table : Modifiez server/schema.ts, puis lancez npm run db:push (pour envoyer le schéma à Turso instantanément).
  2. Coder l'API : Ajoutez une route dans server/routes.ts.
  3. Coder le Front : Utilisez fetch('/api/...') dans src/.
  4. 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).