Pourquoi Deployer Next.js sur Fly.io ?
Deployer un application Next.js sur Fly.io permet une mise en production rapide et élastique, avec des coûts optimisés. Cela est particulièrement utile pour les développeurs qui cherchent à rendre leurs applications accessibles et scalables sans avoir besoin d'infrastructure physique. Un cas concret serait de créer un service de gestionnaire de tâches en ligne où les utilisateurs peuvent ajouter, modifier et supprimer des tâches.
Prerequis
- Connaissance avancée du langage JavaScript
- Familiarité avec le framework Next.js
- Installation de Node.js v14.0 ou plus tard
- Installation de Docker
Concepts fondamentaux
1. Fly.io
Fly.io est une plateforme d'hébergement pour des applications web et microservices. Il permet de déployer, évoluer et mettre à l'échelle des applications en quelques minutes.
## Installation de Fly CLI
curl -L https://fly.io/install.sh | sh
2. Next.js App Router
Next.js 13 introduit le nouveau App Router qui permet une structure de routing plus modulaire et efficace, basée sur les fichiers du système de fichiers.
// pages/dashboard/index.js
import Link from 'next/link';
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Link href="/tasks">
<a>Gestion des tâches</a>
</Link>
</div>
);
}
3. Variables d'environnement
Fly.io permet de définir des variables d'environnement pour votre application, ce qui est utile pour stocker des secrets ou des configurations spécifiques à l'environnement.
## Définir une variable d'environnement dans Fly.io
flyctl config set DATABASE_URL=postgres://user:password@host:port/dbname
Mise en pratique : projet fil rouge
Nous allons créer un simple gestionnaire de tâches en ligne. Le projet comprendra les fonctionnalités suivantes :
- Ajouter une nouvelle tâche
- Modifier une tâche existante
- Supprimer une tâche
Étape 1 : Initialisation du projet
Commencez par initialiser un nouveau projet Next.js.
## Créer un nouveau projet Next.js
npx create-next-app@latest task-manager
cd task-manager
Étape 2 : Configuration de la base de données
Nous utiliserons PostgreSQL pour stocker les tâches. Créez une base de données et configurez les variables d'environnement.
## Créer une base de données PostgreSQL
createdb task_manager
## Définir les variables d'environnement dans fly.toml
[env]
DATABASE_URL = "postgres://user:password@host:port/task_manager"
Étape 3 : Connexion à la base de données
Créez un fichier db.js pour gérer la connexion à la base de données.
// db.js
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
export default pool;
Étape 4 : Création des modèles
Créez un fichier taskModel.js pour définir les opérations CRUD sur la tâche.
// taskModel.js
import db from './db';
export const getTasks = async () => {
const result = await db.query('SELECT * FROM tasks');
return result.rows;
};
export const createTask = async (task) => {
const { title, description } = task;
const result = await db.query(
'INSERT INTO tasks (title, description) VALUES ($1, $2) RETURNING *',
[title, description]
);
return result.rows[0];
};
export const updateTask = async (id, task) => {
const { title, description } = task;
const result = await db.query(
'UPDATE tasks SET title=$1, description=$2 WHERE id=$3 RETURNING *',
[title, description, id]
);
return result.rows[0];
};
export const deleteTask = async (id) => {
const result = await db.query('DELETE FROM tasks WHERE id=$1', [id]);
return result.rowCount > 0;
};
Étape 5 : Création des routes API
Créez des fichiers pour les routes API dans pages/api.
// pages/api/tasks.js
import { getTasks, createTask, updateTask, deleteTask } from '../../taskModel';
export default async function handler(req, res) {
const { method, body } = req;
switch (method) {
case 'GET':
try {
const tasks = await getTasks();
res.status(200).json(tasks);
} catch (error) {
res.status(500).json({ message: error.message });
}
break;
case 'POST':
try {
const newTask = await createTask(body);
res.status(201).json(newTask);
} catch (error) {
res.status(400).json({ message: error.message });
}
break;
default:
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}
nextjs
// pages/api/tasks/[id].js
import { getTasks, updateTask, deleteTask } from '../../taskModel';
export default async function handler(req, res) {
const { method, body, query: { id } } = req;
switch (method) {
case 'GET':
try {
const task = await getTasks();
res.status(200).json(task);
} catch (error) {
res.status(500).json({ message: error.message });
}
break;
case 'PUT':
try {
const updatedTask = await updateTask(id, body);
res.status(200).json(updatedTask);
} catch (error) {
res.status(400).json({ message: error.message });
}
break;
case 'DELETE':
try {
const deleted = await deleteTask(id);
if (deleted) {
res.status(204).end();
} else {
res.status(404).json({ message: 'Tâche non trouvée' });
}
} catch (error) {
res.status(500).json({ message: error.message });
}
break;
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}
Étape 6 : Création des composants UI
Créez les composants React pour afficher et modifier les tâches.
// pages/index.js
import { useState, useEffect } from 'react';
import axios from 'axios';
const Home = () => {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState({ title: '', description: '' });
const [editingTask, setEditingTask] = useState(null);
useEffect(() => {
fetchTasks();
}, []);
const fetchTasks = async () => {
try {
const response = await axios.get('/api/tasks');
setTasks(response.data);
} catch (error) {
console.error('Erreur lors de la récupération des tâches', error);
}
};
const handleAddTask = async () => {
try {
await axios.post('/api/tasks', newTask);
fetchTasks();
setNewTask({ title: '', description: '' });
} catch (error) {
console.error('Erreur lors de l\'ajout d\'une tâche', error);
}
};
const handleEditTask = async () => {
try {
await axios.put(`/api/tasks/${editingTask.id}`, editingTask);
fetchTasks();
setEditingTask(null);
} catch (error) {
console.error('Erreur lors de la mise à jour d\'une tâche', error);
}
};
const handleDeleteTask = async (id) => {
try {
await axios.delete(`/api/tasks/${id}`);
fetchTasks();
} catch (error) {
console.error('Erreur lors de la suppression d\'une tâche', error);
}
};
return (
<div>
<h1>Gestionnaire de Tâches</h1>
{editingTask ? (
<form onSubmit={handleEditTask}>
<input
type="text"
value={editingTask.title}
onChange={(e) => setEditingTask({ ...editingTask, title: e.target.value })}
required
/>
<textarea
value={editingTask.description}
onChange={(e) => setEditingTask({ ...editingTask, description: e.target.value })}
required
></textarea>
<button type="submit">Enregistrer</button>
</form>
) : (
<form onSubmit={handleAddTask}>
<input
type="text"
value={newTask.title}
onChange={(e) => setNewTask({ ...newTask, title: e.target.value })}
required
/>
<textarea
value={newTask.description}
onChange={(e) => setNewTask({ ...newTask, description: e.target.value })}
required
></textarea>
<button type="submit">Ajouter</button>
</form>
)}
<ul>
{tasks.map((task) => (
<li key={task.id}>
{task.title} - {task.description}
<button onClick={() => setEditingTask(task)}>Modifier</button>
<button onClick={() => handleDeleteTask(task.id)}>Supprimer</button>
</li>
))}
</ul>
</div>
);
};
export default Home;
Erreurs frequentes et debugging
1. Error: connect ECONNREFUSED 127.0.0.1:5432
Cause : La base de données PostgreSQL n'est pas démarrée.
## Démarrer la base de données PostgreSQL
pg_ctl -D /usr/local/var/postgres start
2. Error: listen EADDRINUSE :::3000
Cause : Le port 3000 est déjà utilisé par une autre application.
## Trouver l'application utilisant le port 3000 et la terminer
lsof -i :3000
kill -9 <PID>
3. Error: connect ECONNREFUSED <DATABASE_URL>
Cause : La chaîne de connexion à la base de données est incorrecte.
## Vérifier la chaîne de connexion dans fly.toml
flyctl config get DATABASE_URL
Pour aller plus loin
- Déploiement sur un domaine personnalisé : Apprenez comment déployer votre application Next.js sur un domaine personnalisé avec Fly.io.
- Intégration avec des services externes : Intégrez des services externes comme Google Maps ou OpenAI pour enrichir votre application.
- Tests automatisés : Ajoutez des tests automatisés à votre projet en utilisant Jest et React Testing Library.
Défi pratique
Déployez votre gestionnaire de tâches sur Fly.io en suivant les étapes ci-dessus. Assurez-vous que la base de données fonctionne correctement, que l'API est accessible via les routes /api/tasks et /api/tasks/[id], et que l'interface utilisateur permet d'ajouter, modifier et supprimer des tâches.