Construire des APIs Backend Cloud Type-Safe avec Encore.ts : Du Zero a la Production

AI Bot
Par AI Bot ·

Chargement du lecteur de synthèse vocale...

Votre framework backend devrait gerer l'infrastructure, pas l'inverse. Encore.ts est le framework backend TypeScript qui vous offre des APIs type-safe, un provisionnement automatique de l'infrastructure et une observabilite integree — le tout sans ecrire une seule ligne de Terraform ou de configuration Docker. Dans ce tutoriel, vous allez construire une API complete de gestion de taches de A a Z.

Ce que vous allez apprendre

A la fin de ce tutoriel, vous serez capable de :

  • Configurer un projet Encore.ts avec TypeScript
  • Definir des endpoints API type-safe avec validation automatique des requetes/reponses
  • Creer et gerer des bases de donnees SQL avec des migrations
  • Implementer la messagerie pub/sub entre services
  • Planifier des taches cron pour les operations recurrentes
  • Utiliser l'observabilite integree d'Encore (tracing, logging, metriques)
  • Deployer dans le cloud avec provisionnement automatique de l'infrastructure

Prerequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 18+ installe (node --version)
  • Une experience en TypeScript (types, generiques, async/await)
  • Des notions de base sur les API REST (methodes HTTP, codes de statut, JSON)
  • Un editeur de code — VS Code ou Cursor recommande
  • Un compte Encore (offre gratuite disponible sur encore.dev)

Pourquoi Encore.ts ?

L'ecosysteme backend TypeScript propose de nombreuses options — Express, Fastify, Hono, NestJS — alors pourquoi choisir Encore ? Voici ce qui le differencie :

FonctionnaliteEncore.tsFastifyNestJSHono
TypageBout en bout, compile-timeManuel avec schemasBase sur les decorateursManuel
InfrastructureProvisionnement automatiqueA faire soi-memeA faire soi-memeA faire soi-meme
Base de donneesMigrations integreesORM externeORM externeExterne
Pub/SubPrimitive integreeBibliotheque externeModule externeNon inclus
Taches CronPrimitive integreeScheduleur externeScheduleur externeNon inclus
ObservabiliteTracing automatiqueConfiguration manuelleConfiguration manuelleManuelle
Dev localEmulation cloud completeDocker manuelDocker manuelManuel

Encore adopte une approche fondamentalement differente : vous declarez l'infrastructure dont vous avez besoin dans votre code TypeScript, et Encore la provisionne automatiquement — aussi bien localement pour le developpement que dans le cloud pour la production. Pas de Terraform, pas de fichiers Docker Compose, pas de manifestes Kubernetes.


Etape 1 : Installer le CLI Encore et creer un projet

Commencez par installer le CLI Encore :

# macOS
brew install encoredev/tap/encore
 
# Linux
curl -L https://encore.dev/install.sh | bash
 
# Windows (WSL2)
curl -L https://encore.dev/install.sh | bash

Verifiez l'installation :

encore version

Maintenant, creez un nouveau projet :

encore app create task-api --example=ts/empty
cd task-api

Cela cree un projet Encore.ts minimal. Examinons la structure :

task-api/
├── encore.app          # Configuration de l'app Encore
├── package.json
├── tsconfig.json
└── ... (fichiers de demarrage minimaux)

Le fichier encore.app contient l'identifiant et la configuration de votre application. Contrairement aux frameworks traditionnels, il n'y a pas de fichier index.ts ou de bootstrap de serveur — Encore decouvre vos services automatiquement.


Etape 2 : Creer votre premier service et endpoint API

Dans Encore, un service est simplement un repertoire contenant un fichier encore.service.ts. Creons un service de gestion de taches :

mkdir task

Creez la definition du service :

// task/encore.service.ts
import { Service } from "encore.dev/service";
 
export default new Service("task");

Maintenant, creez les endpoints API :

// task/task.ts
import { api } from "encore.dev/api";
 
// Definition de l'interface Task
interface Task {
  id: number;
  title: string;
  description: string;
  completed: boolean;
  createdAt: string;
}
 
// Stockage en memoire (nous ajouterons une base de donnees plus tard)
let tasks: Task[] = [];
let nextId = 1;
 
// Creer une nouvelle tache
export const create = api(
  { expose: true, method: "POST", path: "/tasks" },
  async (req: { title: string; description: string }): Promise<Task> => {
    const task: Task = {
      id: nextId++,
      title: req.title,
      description: req.description,
      completed: false,
      createdAt: new Date().toISOString(),
    };
    tasks.push(task);
    return task;
  }
);
 
// Lister toutes les taches
export const list = api(
  { expose: true, method: "GET", path: "/tasks" },
  async (): Promise<{ tasks: Task[] }> => {
    return { tasks };
  }
);
 
// Obtenir une tache par ID
export const get = api(
  { expose: true, method: "GET", path: "/tasks/:id" },
  async (req: { id: number }): Promise<Task> => {
    const task = tasks.find((t) => t.id === req.id);
    if (!task) {
      throw new Error(`Tache ${req.id} introuvable`);
    }
    return task;
  }
);

Remarquez quelque chose d'important : il n'y a pas de configuration de serveur, pas de middleware, pas d'enregistrement de routes. Vous definissez simplement des fonctions et Encore gere le reste. La fonction api() fournit :

  • Validation automatique des requetes — si un champ requis est manquant, Encore renvoie une erreur 400
  • Parametres de chemin type-safe:id est automatiquement parse comme un nombre
  • Serialisation automatique — votre type de retour est serialise en JSON

Lancez le serveur de developpement :

encore run

Encore demarre un environnement de developpement local avec un tableau de bord integre accessible sur http://localhost:9400. Vous pouvez tester votre API :

curl -X POST http://localhost:4000/tasks \
  -H "Content-Type: application/json" \
  -d '{"title": "Apprendre Encore", "description": "Construire une API de taches"}'

Etape 3 : Ajouter une base de donnees PostgreSQL

Le stockage en memoire est suffisant pour le prototypage, mais ajoutons une vraie base de donnees. Avec Encore, vous n'avez pas besoin d'installer PostgreSQL ni d'ecrire des fichiers Docker Compose — il suffit de declarer la base de donnees dans votre code :

// task/db.ts
import { SQLDatabase } from "encore.dev/storage/sqldb";
 
// Declarer la base de donnees — Encore la provisionne automatiquement
const db = new SQLDatabase("taskdb", {
  migrations: "./migrations",
});
 
export default db;

Creez le repertoire des migrations et la premiere migration :

mkdir -p task/migrations
-- task/migrations/001_create_tasks.up.sql
CREATE TABLE tasks (
    id BIGSERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    description TEXT NOT NULL DEFAULT '',
    completed BOOLEAN NOT NULL DEFAULT false,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
 
CREATE INDEX idx_tasks_completed ON tasks(completed);

Mettez a jour l'API pour utiliser la base de donnees :

// task/task.ts
import { api } from "encore.dev/api";
import db from "./db";
 
interface Task {
  id: number;
  title: string;
  description: string;
  completed: boolean;
  createdAt: string;
}
 
interface CreateParams {
  title: string;
  description: string;
}
 
interface ListResponse {
  tasks: Task[];
}
 
// Creer une nouvelle tache
export const create = api(
  { expose: true, method: "POST", path: "/tasks" },
  async (req: CreateParams): Promise<Task> => {
    const row = await db.queryRow`
      INSERT INTO tasks (title, description)
      VALUES (${req.title}, ${req.description})
      RETURNING id, title, description, completed, created_at
    `;
    return rowToTask(row!);
  }
);
 
// Lister toutes les taches
export const list = api(
  { expose: true, method: "GET", path: "/tasks" },
  async (): Promise<ListResponse> => {
    const rows = await db.query`
      SELECT id, title, description, completed, created_at
      FROM tasks
      ORDER BY created_at DESC
    `;
    const tasks: Task[] = [];
    for await (const row of rows) {
      tasks.push(rowToTask(row));
    }
    return { tasks };
  }
);
 
// Obtenir une tache par ID
export const get = api(
  { expose: true, method: "GET", path: "/tasks/:id" },
  async (req: { id: number }): Promise<Task> => {
    const row = await db.queryRow`
      SELECT id, title, description, completed, created_at
      FROM tasks WHERE id = ${req.id}
    `;
    if (!row) throw new Error(`Tache ${req.id} introuvable`);
    return rowToTask(row);
  }
);
 
// Mettre a jour une tache
export const update = api(
  { expose: true, method: "PUT", path: "/tasks/:id" },
  async (req: {
    id: number;
    title?: string;
    description?: string;
    completed?: boolean;
  }): Promise<Task> => {
    const row = await db.queryRow`
      UPDATE tasks SET
        title = COALESCE(${req.title ?? null}, title),
        description = COALESCE(${req.description ?? null}, description),
        completed = COALESCE(${req.completed ?? null}, completed)
      WHERE id = ${req.id}
      RETURNING id, title, description, completed, created_at
    `;
    if (!row) throw new Error(`Tache ${req.id} introuvable`);
    return rowToTask(row);
  }
);
 
// Supprimer une tache
export const remove = api(
  { expose: true, method: "DELETE", path: "/tasks/:id" },
  async (req: { id: number }): Promise<void> => {
    await db.exec`DELETE FROM tasks WHERE id = ${req.id}`;
  }
);
 
// Helper pour convertir une ligne de base de donnees en Task
function rowToTask(row: any): Task {
  return {
    id: row.id,
    title: row.title,
    description: row.description,
    completed: row.completed,
    createdAt: row.created_at.toISOString(),
  };
}

Quand vous relancez encore run, Encore provisionne automatiquement une base de donnees PostgreSQL locale, execute les migrations et connecte votre service. Pas de Docker, pas de chaines de connexion, pas de variables d'environnement.


Etape 4 : Ajouter un service de notifications avec Pub/Sub

Ajoutons un second service qui envoie des notifications quand une tache est terminee. D'abord, definissons un topic pub/sub :

// task/events.ts
import { Topic } from "encore.dev/pubsub";
 
// Definition du type d'evenement
export interface TaskCompletedEvent {
  taskId: number;
  title: string;
  completedAt: string;
}
 
// Creation du topic
export const taskCompleted = new Topic<TaskCompletedEvent>("task-completed", {
  deliveryGuarantee: "at-least-once",
});

Mettez a jour l'endpoint update pour publier un evenement quand une tache est terminee :

// Dans task/task.ts — ajoutez cet import en haut
import { taskCompleted } from "./events";
 
// Mettez a jour la fonction update pour publier des evenements
export const update = api(
  { expose: true, method: "PUT", path: "/tasks/:id" },
  async (req: {
    id: number;
    title?: string;
    description?: string;
    completed?: boolean;
  }): Promise<Task> => {
    const row = await db.queryRow`
      UPDATE tasks SET
        title = COALESCE(${req.title ?? null}, title),
        description = COALESCE(${req.description ?? null}, description),
        completed = COALESCE(${req.completed ?? null}, completed)
      WHERE id = ${req.id}
      RETURNING id, title, description, completed, created_at
    `;
    if (!row) throw new Error(`Tache ${req.id} introuvable`);
    const task = rowToTask(row);
 
    // Publier un evenement si la tache est marquee comme terminee
    if (req.completed === true) {
      await taskCompleted.publish({
        taskId: task.id,
        title: task.title,
        completedAt: new Date().toISOString(),
      });
    }
 
    return task;
  }
);

Creez maintenant le service de notification :

mkdir notification
// notification/encore.service.ts
import { Service } from "encore.dev/service";
 
export default new Service("notification");
// notification/notification.ts
import { Subscription } from "encore.dev/pubsub";
import { taskCompleted, TaskCompletedEvent } from "../task/events";
 
// S'abonner aux evenements de completion de taches
const _ = new Subscription(taskCompleted, "notify-on-complete", {
  handler: async (event: TaskCompletedEvent) => {
    console.log(
      `Tache terminee : "${event.title}" (ID: ${event.taskId}) a ${event.completedAt}`
    );
    // En production, vous enverriez un email, un message Slack, etc.
  },
});

C'est tout. Encore gere :

  • La serialisation des messages avec typage
  • Les garanties de livraison (au moins une fois)
  • L'emulation locale avec un broker de messages en memoire
  • Le provisionnement cloud de l'infrastructure pub/sub reelle (ex. GCP Pub/Sub, AWS SNS/SQS)

Etape 5 : Ajouter des taches Cron pour les operations recurrentes

Ajoutons une tache cron qui envoie un resume quotidien des taches incompletes :

// task/cron.ts
import { CronJob } from "encore.dev/cron";
import db from "./db";
 
// Executer chaque jour a 9h00 UTC
const dailySummary = new CronJob("daily-task-summary", {
  title: "Resume quotidien des taches",
  schedule: "0 9 * * *",
  endpoint: sendDailySummary,
});
 
async function sendDailySummary() {
  const row = await db.queryRow`
    SELECT
      COUNT(*) FILTER (WHERE completed = false) as pending,
      COUNT(*) FILTER (WHERE completed = true) as done,
      COUNT(*) as total
    FROM tasks
  `;
 
  console.log(
    `Resume quotidien — En attente : ${row!.pending}, Terminees : ${row!.done}, Total : ${row!.total}`
  );
}

Les taches cron d'Encore sont :

  • Declarees dans le code — pas besoin de scheduleur externe ou de crontab
  • Automatiquement enregistrees — Encore les decouvre a la compilation
  • Visibles dans le dashboard — vous pouvez voir l'historique d'execution et les logs

Etape 6 : Authentification et autorisation

Encore fournit un pattern de gestion d'authentification integre. Ajoutons une authentification par cle API :

// auth/encore.service.ts
import { Service } from "encore.dev/service";
 
export default new Service("auth");
// auth/auth.ts
import { authHandler } from "encore.dev/auth";
import { Header } from "encore.dev/api";
 
interface AuthParams {
  authorization: Header<"Authorization">;
}
 
interface AuthData {
  userId: string;
  role: string;
}
 
// Definir le handler d'authentification
export const auth = authHandler(
  async (params: AuthParams): Promise<AuthData> => {
    const token = params.authorization?.replace("Bearer ", "");
 
    if (!token) {
      throw new Error("Token d'autorisation manquant");
    }
 
    // En production, verifiez un JWT ou recherchez une cle API
    // Pour ce tutoriel, nous utilisons une verification simple
    if (token === "admin-secret-key") {
      return { userId: "admin", role: "admin" };
    }
 
    throw new Error("Token invalide");
  }
);

Protegez maintenant les endpoints en ajoutant auth: true :

// Dans task/task.ts — mettez a jour l'endpoint create
export const create = api(
  { expose: true, auth: true, method: "POST", path: "/tasks" },
  async (req: CreateParams): Promise<Task> => {
    // req inclut maintenant les donnees d'auth
    // ... meme implementation
  }
);

Les requetes vers les endpoints proteges necessitent desormais un header Authorization valide.


Etape 7 : Tester votre API

Encore dispose d'un support de tests integre. Creez un fichier de test :

// task/task.test.ts
import { describe, it, expect } from "vitest";
import { create, list, get, update, remove } from "./task";
 
describe("API des Taches", () => {
  it("devrait creer une tache", async () => {
    const task = await create({
      title: "Tache de test",
      description: "Une tache de test",
    });
 
    expect(task.title).toBe("Tache de test");
    expect(task.description).toBe("Une tache de test");
    expect(task.completed).toBe(false);
    expect(task.id).toBeGreaterThan(0);
  });
 
  it("devrait lister les taches", async () => {
    const result = await list();
    expect(result.tasks.length).toBeGreaterThan(0);
  });
 
  it("devrait mettre a jour une tache", async () => {
    const task = await create({
      title: "A completer",
      description: "Sera completee",
    });
 
    const updated = await update({
      id: task.id,
      completed: true,
    });
 
    expect(updated.completed).toBe(true);
  });
 
  it("devrait supprimer une tache", async () => {
    const task = await create({
      title: "A supprimer",
      description: "Sera supprimee",
    });
 
    await remove({ id: task.id });
 
    await expect(get({ id: task.id })).rejects.toThrow();
  });
});

Lancez les tests :

encore test ./task/...

Encore provisionne automatiquement une base de donnees de test separee pour chaque execution de tests, execute les migrations et la supprime ensuite. Pas besoin de fixtures de test ni de code de nettoyage.


Etape 8 : Explorer le tableau de bord de developpement local

L'une des fonctionnalites phares d'Encore est le tableau de bord de developpement local. Quand votre application tourne (encore run), ouvrez http://localhost:9400 pour acceder a :

  • Catalogue de services — tous vos services, endpoints et leurs types
  • Explorateur API — testez les endpoints directement depuis le navigateur
  • Diagrammes de flux — visualisez comment les services communiquent
  • Explorateur de traces — inspectez les traces de requetes avec les details de timing
  • Explorateur de base de donnees — parcourez vos tables
  • Moniteur Pub/Sub — visualisez les messages publies et les abonnements

Ce tableau de bord est genere automatiquement a partir de votre code — pas de configuration Swagger, pas de fichiers de specification OpenAPI, pas de documentation manuelle.


Etape 9 : Generer un SDK client

Encore peut generer des SDKs client type-safe pour votre API :

encore gen client task-api --output=./client --lang=typescript

Cela genere un client TypeScript utilisable dans votre frontend :

import Client from "./client";
 
const client = new Client({ baseURL: "http://localhost:4000" });
 
// Entierement type — l'autocompletion de l'IDE fonctionne parfaitement
const task = await client.task.create({
  title: "Depuis le Frontend",
  description: "Cree via le client genere",
});
 
const allTasks = await client.task.list();

Le client genere inclut :

  • Tous les types requete/reponse correspondant a votre backend
  • La serialisation automatique pour les dates, enums, etc.
  • La gestion des erreurs avec des reponses d'erreur typees
  • Le support de l'authentification integre

Etape 10 : Deployer dans le cloud

Deployer une application Encore est remarquablement simple :

git add -A
git commit -m "feat: API de gestion de taches avec base de donnees, pub/sub et cron"
git push encore main

Encore gere tout :

  1. Compile votre application
  2. Provisionne l'infrastructure — base de donnees, topics pub/sub, scheduleurs cron
  3. Configure le reseau — service mesh, load balancing, TLS
  4. Deploie avec zero temps d'arret
  5. Met en place le monitoring — tracing distribue, logging, metriques

Vous pouvez deployer sur Encore Cloud (gere) ou sur votre propre compte AWS ou GCP. Dans les deux cas, Encore provisionne automatiquement les bons services d'infrastructure.

Surveillez votre deploiement depuis le dashboard Encore Cloud :

encore app open

Architecture du projet

Voici ce que nous avons construit :

task-api/
├── encore.app
├── task/
│   ├── encore.service.ts    # Definition du service
│   ├── task.ts              # Endpoints API CRUD
│   ├── db.ts                # Declaration de la base de donnees
│   ├── events.ts            # Definition du topic pub/sub
│   ├── cron.ts              # Definitions des taches cron
│   ├── task.test.ts         # Tests d'integration
│   └── migrations/
│       └── 001_create_tasks.up.sql
├── notification/
│   ├── encore.service.ts    # Definition du service
│   └── notification.ts      # Abonne aux evenements
└── auth/
    ├── encore.service.ts    # Definition du service
    └── auth.ts              # Handler d'authentification

Trois services, une base de donnees, la messagerie pub/sub, des taches cron, l'authentification et le deploiement cloud — le tout en environ 200 lignes de TypeScript. Pas de Terraform, pas de Docker, pas de Kubernetes, pas de variables d'environnement.


Depannage

Problemes courants

"Echec de la migration de la base de donnees" Assurez-vous que votre fichier SQL se termine par .up.sql et est numerote sequentiellement (001, 002, etc.). Verifiez les erreurs de syntaxe dans le SQL.

"Service non decouvert" Verifiez que vous avez un fichier encore.service.ts dans le repertoire du service avec un appel new Service() valide.

"Handler d'authentification non trouve" Le handler d'authentification doit etre exporte depuis un fichier dans un repertoire de service. Assurez-vous que authHandler est importe depuis encore.dev/auth.

"Port deja utilise" Encore utilise le port 4000 par defaut. S'il est occupe, Encore choisira le prochain port disponible. Verifiez la sortie du terminal pour l'URL reelle.


Prochaines etapes

Maintenant que vous avez une application Encore.ts fonctionnelle, voici quelques idees pour l'etendre :

  • Ajouter un frontend en utilisant le SDK client genere avec Next.js ou React
  • Ajouter plus de services — gestion des utilisateurs, analytics, upload de fichiers
  • Implementer des webhooks en utilisant le pub/sub d'Encore pour les integrations externes
  • Configurer des environnements — staging et production avec des configurations differentes
  • Ajouter la gestion des secrets avec encore secret set
  • Explorer les metriques d'Encore pour des metriques metier personnalisees

Tutoriels connexes


Conclusion

Encore.ts represente un changement de paradigme dans le developpement backend. Au lieu d'assembler des dizaines de packages, d'ecrire de l'infrastructure-as-code et de configurer des pipelines CI/CD, vous vous concentrez sur votre logique metier et laissez Encore gerer le reste.

Dans ce tutoriel, vous avez construit une API complete de gestion de taches avec :

  • Des endpoints type-safe avec validation automatique
  • Une base de donnees PostgreSQL avec migrations
  • La messagerie pub/sub entre services
  • Des taches cron pour les operations planifiees
  • L'authentification avec un auth handler
  • Des tests avec provisionnement automatique de base de donnees
  • Le deploiement cloud avec zero configuration d'infrastructure

Le point essentiel a retenir est qu'Encore ne se contente pas de vous economiser du boilerplate — il elimine des categories entieres de travail (provisionnement d'infrastructure, mise en place de l'observabilite, generation de SDK client) qui consomment traditionnellement un temps d'ingenierie considerable. Que vous construisiez un MVP de startup ou que vous evoluiez vers des millions d'utilisateurs, Encore.ts vous offre une base solide et type-safe qui evolue avec vos besoins.


Vous voulez lire plus de tutoriels? Découvrez notre dernier tutoriel sur Zustand + Next.js App Router : Gestion d'État React Moderne du Zéro à la Production.

Discutez de votre projet avec nous

Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.

Trouvons les meilleures solutions pour vos besoins.

Articles connexes

Construire des APIs prêtes pour la production avec Fastify et TypeScript

Apprenez à construire des APIs REST rapides et typées avec Fastify et TypeScript. Ce guide complet couvre la configuration du projet, la validation de schéma, l'authentification, l'intégration de base de données, la gestion des erreurs, les tests et les bonnes pratiques de déploiement.

30 min read·

Construire une API GraphQL typesafe avec Next.js App Router, Yoga et Pothos

Apprenez à construire une API GraphQL entièrement typesafe avec Next.js 15 App Router, GraphQL Yoga et le constructeur de schémas Pothos. Ce tutoriel pratique couvre la conception de schémas, les requêtes, les mutations, le middleware d'authentification et un client React avec urql.

30 min read·