Back to flin
flin

FLIN en pratique : premiers exemples

Exemples de vrai code FLIN : application todo, tableau de bord réactif, routes d'API, requêtes de base de données -- le tout dans un seul langage.

Thales & Claude | March 30, 2026 14 min flin
EN/ FR/ ES
flinexamplestutorialcodereactiveentitiesviews

La théorie, c'est gratuit. Chaque nouveau langage promet la simplicité, chaque nouveau framework prétend résoudre la complexité, et chaque manifeste se lit bien jusqu'à ce que vous essayiez de construire quelque chose de réel. FLIN a publié sa vision, son architecture et sa philosophie. Il est maintenant temps de montrer le code.

Cet article présente six applications FLIN complètes, progressant du trivial au substantiel : un compteur, un hello-world avec binding d'input, une application todo complète avec persistance, un blog avec commentaires, une API avec des endpoints CRUD et un tableau de bord en temps réel avec des requêtes temporelles. Chaque exemple est un programme fonctionnel -- pas une esquisse, pas du pseudocode, pas un extrait simplifié qui cache les parties difficiles.

L'objectif est de répondre à la question que chaque développeur pose face à un nouveau langage : à quoi ça ressemble concrètement quand je m'assieds et que je construis quelque chose ?

Exemple 1 : le compteur (3 lignes)

Chaque framework d'interface commence avec un compteur. Voici celui de FLIN :

flincount = 0

<button click={count++}>{count}</button>

Trois lignes. Une variable réactive. Un élément HTML. Un gestionnaire d'événement. Sauvegardez ceci sous counter.flin, lancez flin dev, et le navigateur s'ouvre avec un bouton affichant 0. Cliquez dessus. Le nombre s'incrémente. Il n'y a rien d'autre à configurer, rien d'autre à installer, rien d'autre à comprendre.

La déclaration count = 0 crée une variable réactive. La réactivité est le comportement par défaut dans FLIN -- il n'y a pas de useState, pas de $state, pas de signal(). Chaque variable est suivie par le compilateur. Quand count change, chaque expression de vue qui référence count se réévalue automatiquement.

L'attribut click={count++} est un gestionnaire d'événement en ligne. Pas de onClick, pas de on:click, pas de setup de délégation d'événements. Le nom de l'attribut correspond au nom de l'événement DOM, et la valeur est une expression qui s'exécute quand l'événement se déclenche.

L'interpolation {count} rend la valeur courante. Quand count change, ce noeud texte se met à jour. Pas de diff de DOM virtuel pour un seul noeud texte -- le compilateur sait exactement quel noeud DOM mettre à jour parce qu'il suit la dépendance au moment de la compilation.

Exemple 2 : binding d'input (5 lignes)

Le two-way binding de FLIN rend la gestion des formulaires sans effort :

flinname = ""

<div>
    <input placeholder="Votre nom" value={name}>
    <h1>Bonjour, {if name then name else "Monde"} !</h1>
</div>

L'attribut value={name} crée un two-way binding. Quand l'utilisateur tape dans l'input, name se met à jour. Quand name se met à jour, le contenu du <h1> se re-rend. L'expression de style ternaire if/then/else gère l'état vide en ligne.

Comparez avec React, où vous auriez besoin de useState, d'un gestionnaire onChange qui appelle setName(e.target.value) et d'un pattern de composant contrôlé que beaucoup de débutants trouvent déroutant. Dans FLIN, le binding est implicite dans la syntaxe. Vous assignez une variable à value, et le runtime gère la synchronisation dans les deux directions.

Exemple 3 : l'application todo (43 lignes)

L'application todo est le "hello world" des frameworks frontend. Elle teste la persistance, le rendu de listes, le filtrage, la gestion d'événements et le stylisme conditionnel. Voici l'implémentation FLIN complète :

flintodos = []
filter = "all"
newTodo = ""

entity Todo {
    title: text
    done: bool = false
    created: time = now
}

filtered = match filter {
    "all" -> Todo.all
    "active" -> Todo.where(done == false)
    "completed" -> Todo.where(done == true)
}

<main>
    <h1>Mes Todos</h1>

    <input value={newTodo} placeholder="Que faut-il faire ?"
           enter={save Todo { title: newTodo }; newTodo = ""}>

    <nav>
        <button click={filter = "all"}>Tous</button>
        <button click={filter = "active"}>Actifs</button>
        <button click={filter = "completed"}>Faits</button>
    </nav>

    {for todo in filtered}
        <div class="todo-item">
            <input type="checkbox" checked={todo.done}
                   change={todo.done = !todo.done; save todo}>
            <span class={if todo.done then "done" else ""}>{todo.title}</span>
            <button click={delete todo}>x</button>
        </div>
    {/for}

    <footer>{Todo.where(done == false).count} éléments restants</footer>
</main>

Parcourons les constructions clés.

Le mot-clé entity. entity Todo { ... } déclare un type de données persistant. Le compilateur FLIN génère une table de base de données dans FlinDB, enregistre les méthodes de requête (Todo.all, Todo.where(...), Todo.count) et active le suivi temporel. Les = false et = now sont des valeurs par défaut -- done est par défaut à false, created à l'horodatage courant.

L'expression match. C'est la construction de pattern matching de FLIN. En fonction de la valeur de filter, elle sélectionne la requête de base de données appropriée. Todo.all retourne tous les todos. Todo.where(done == false) génère une requête filtrée. Parce que filter est réactif, le changer réévalue l'expression match, qui relance la requête, qui met à jour la vue.

Le mot-clé save. save Todo { title: newTodo } crée un nouvel enregistrement Todo dans la base de données. save todo (sans constructeur de type) met à jour un enregistrement existant. Un seul mot-clé pour la création et la mise à jour -- le runtime vérifie si l'entité a un ID pour déterminer quelle opération effectuer.

Le mot-clé delete. delete todo supprime l'enregistrement de la base de données et déclenche une mise à jour réactive qui le retire de la vue.

L'événement enter. FLIN ajoute des noms d'événements sémantiques au-delà des événements DOM standard. enter se déclenche quand l'utilisateur appuie sur la touche Entrée dans un champ input. Pas de gestionnaire onKeyDown vérifiant event.key === 'Enter'.

L'application entière -- interface, base de données, filtrage, opérations CRUD -- tient dans un seul fichier avec zéro import, zéro configuration et zéro dépendance externe.

Exemple 4 : un blog avec commentaires (65 lignes)

Un blog exerce les relations entre entités, le routage basé sur les fichiers, le formatage de dates et le rendu de contenu. Voici la page d'accueil :

flinentity Post {
    title: text
    content: semantic text
    slug: text
    published: bool = false
    created: time = now
}

entity Comment {
    post: Post
    author: text
    content: text
    created: time = now
}

posts = Post.where(published == true).order(created, "desc")

<main>
    <h1>Mon Blog</h1>

    {for post in posts}
        <article>
            <h2><a href="/post/{post.slug}">{post.title}</a></h2>
            <p>{post.content.slice(0, 200)}...</p>
            <small>{post.created.format("D MMMM YYYY")}</small>
        </article>
    {/for}
</main>

Et la page d'article individuel, sauvegardée sous post/[slug].flin :

flinpost = Post.where(slug == params.slug).first
comments = Comment.where(post.id == post.id)
newComment = ""
author = ""

{if post}
    <article>
        <h1>{post.title}</h1>
        <time>{post.created.format("D MMMM YYYY")}</time>
        <div class="content">{post.content}</div>
    </article>

    <section class="comments">
        <h2>Commentaires ({comments.count})</h2>

        {for comment in comments}
            <div class="comment">
                <strong>{comment.author}</strong>
                <p>{comment.content}</p>
                <small>{comment.created.from_now}</small>
            </div>
        {/for}

        <form>
            <input placeholder="Votre nom" value={author}>
            <textarea placeholder="Votre commentaire" value={newComment}></textarea>
            <button click={
                save Comment { post: post, author: author, content: newComment };
                newComment = "";
                author = ""
            }>Poster un commentaire</button>
        </form>
    </section>
{else}
    <h1>Article non trouvé</h1>
{/if}

Plusieurs nouveaux concepts apparaissent ici.

Texte sémantique. L'annotation de type content: semantic text indique à FLIN de générer automatiquement des embeddings vectoriels pour ce champ, permettant la recherche sémantique à travers le contenu des articles. Ce n'est pas un service séparé -- FlinDB gère l'embedding et le stockage vectoriel nativement.

Relations entre entités. post: Post dans l'entité Comment crée une relation de clé étrangère. FLIN gère la contrainte, la jointure et la suppression en cascade automatiquement.

Routage basé sur les fichiers. Le nom de fichier post/[slug].flin crée une route dynamique. La variable params.slug est automatiquement peuplée depuis l'URL. C'est le même pattern utilisé par Next.js et SvelteKit, mais sans framework -- c'est intégré au langage.

Formatage temporel. post.created.format("D MMMM YYYY") et comment.created.from_now sont des méthodes intégrées au type time. Pas de Moment.js. Pas de date-fns. Pas de bibliothèque de formatage à importer.

Exemple 5 : endpoints d'API (30 lignes)

FLIN n'est pas qu'un langage frontend. Il gère les endpoints d'API HTTP avec la même approche de routage basé sur les fichiers :

flin// api/users.flin
entity User {
    name: text
    email: text
    role: text = "user"
    active: bool = true
}

route GET {
    User.where(active == true)
}

route POST {
    user = User {
        name: body.name,
        email: body.email,
        role: body.role || "user"
    }
    save user
    user
}
flin// api/users/[id].flin
route GET {
    User.find(params.id)
}

route PUT {
    user = User.find(params.id)
    if user {
        user.name = body.name || user.name
        user.email = body.email || user.email
        user.role = body.role || user.role
        save user
    }
    user
}

route DELETE {
    user = User.find(params.id)
    if user {
        delete user
    }
    { success: true }
}

Le mot-clé route déclare un gestionnaire de méthode HTTP. body est le corps de la requête parsé (JSON). params fournit les paramètres d'URL. La valeur de retour est automatiquement sérialisée en JSON avec le code de statut approprié -- 200 pour les lectures réussies, 201 pour les créations, 404 quand User.find retourne none.

Dans une application Express.js, cette même API nécessiterait : Express lui-même, un middleware de parsing de body, un routeur, un pilote de base de données, un ORM ou constructeur de requêtes, un middleware de gestion des erreurs et de la logique de validation. La version FLIN fait trente lignes sur deux fichiers avec zéro dépendance.

Exemple 6 : un tableau de bord avec voyage dans le temps (35 lignes)

La base de données temporelle de FLIN permet des requêtes que la plupart des langages ne peuvent pas exprimer sans une base de données de séries temporelles dédiée. Voici un tableau de bord de métriques qui compare les valeurs actuelles avec les valeurs historiques :

flinentity Metric {
    name: text
    value: number
    recorded: time = now
}

revenue_today = Metric.where(name == "revenue").first
revenue_yesterday = revenue_today @ yesterday

change = if revenue_yesterday then
    ((revenue_today.value - revenue_yesterday.value) / revenue_yesterday.value) * 100
else
    0

<div class="dashboard">
    <div class="metric">
        <h3>Chiffre d'affaires</h3>
        <span class="value">{revenue_today.value.format()}</span>
        <span class={if change > 0 then "up" else "down"}>
            {change.to_fixed(1)}%
        </span>
    </div>

    <div class="history">
        <h3>Historique du chiffre d'affaires</h3>
        {for version in revenue_today.history.last(7)}
            <div class="history-item">
                <span>{version.recorded.format("MMM D")}</span>
                <span>{version.value.format()}</span>
            </div>
        {/for}
    </div>
</div>

L'opérateur @ est la syntaxe de requête temporelle de FLIN. revenue_today @ yesterday récupère l'état de la métrique de revenu tel qu'il existait hier. Ce n'est pas une deuxième requête de base de données vers un stockage de séries temporelles séparé -- FlinDB maintient l'historique complet de chaque entité automatiquement.

revenue_today.history retourne l'historique complet des versions de l'enregistrement. .last(7) prend les sept versions les plus récentes. La fonctionnalité d'historique entière ne nécessite aucune configuration supplémentaire -- chaque entité dans FLIN a le suivi temporel activé par défaut.

Dans une pile traditionnelle, construire ce tableau de bord nécessiterait : une base de données de séries temporelles (InfluxDB, TimescaleDB, ou une table d'audit personnalisée), un cron job ou un déclencheur pour capturer les valeurs, un endpoint backend pour calculer les deltas et de la gestion d'état frontend pour combiner les données courantes et historiques. FLIN condense tout cela dans l'opérateur @ et la propriété .history.

Les patterns

Après six exemples, certains patterns émergent qui définissent l'approche de FLIN au développement d'applications.

Un fichier, une responsabilité. Chaque fichier .flin est une unité autonome -- une page, un endpoint d'API ou un composant. Il n'y a pas de séparation entre "frontend" et "backend". Le fichier déclare ses entités, ses requêtes, ses gestionnaires d'événements et sa vue.

La réactivité est invisible. Les variables sont réactives par défaut. Le développeur ne pense jamais à la gestion d'état, aux abonnements ou au re-rendu. Le compilateur suit les dépendances et met à jour la vue automatiquement.

La persistance est un mot-clé. save et delete sont des opérations au niveau du langage, pas des appels de bibliothèque. Le développeur ne pense pas aux connexions de base de données, aux transactions ou à la sérialisation.

Les requêtes sont des chaînes de méthodes. Entity.where(...), Entity.all, Entity.find(...), Entity.count -- les requêtes de base de données se lisent comme de l'anglais. Pas de chaînes SQL, pas de constructeurs de requêtes, pas de configuration d'ORM.

Le temps est un concept de première classe. Chaque entité a un historique. L'opérateur @ interroge les états passés. La propriété .history retourne le journal complet des versions. Le voyage dans le temps n'est pas une fonctionnalité avancée -- c'est le fonctionnement de FLIN.

La compilation derrière la simplicité

Chacun de ces exemples est compilé à travers le pipeline en quatre phases de FLIN. Voici ce qui se passe quand le compilateur traite l'application todo :

Phase 1 : analyse lexicale. Le lexer lit le fichier source caractère par caractère et produit un flux de tokens. Le mot-clé entity devient TokenKind::Keyword(Keyword::Entity). Le {for ouvrant devient un token de contrôle de vue. La balise <main> devient un token TagOpen suivi d'un identifiant.

Phase 2 : analyse syntaxique. Le parser construit un arbre syntaxique abstrait à partir du flux de tokens. Le bloc entity Todo { ... } devient un noeud EntityDeclaration. Le bloc <main>...</main> devient un noeud ViewElement avec des noeuds enfants pour chaque élément HTML et bloc de contrôle de flux.

Phase 3 : analyse sémantique. Le vérificateur de types valide l'AST. Il vérifie que todo.done est un booléen (donc !todo.done est valide), que Todo.where(done == false) retourne une liste de Todos (donc {for todo in filtered} est correctement typé), et que save Todo { title: newTodo } fournit tous les champs requis.

Phase 4 : génération de code. Le générateur de code émet des instructions bytecode pour la machine virtuelle FLIN. Les déclarations d'entités deviennent des instructions d'enregistrement de schéma. Les éléments de vue deviennent des instructions de construction DOM. Les bindings réactifs deviennent des instructions de suivi de dépendances. Les opérations de base de données deviennent des appels à l'API FlinDB.

Le développeur écrit 43 lignes. Le compilateur produit des centaines d'instructions bytecode. Le runtime les exécute, gérant la réactivité, la persistance et le rendu en coulisses.

Ce que vous ne pouvez pas faire (encore)

L'honnêteté sur les limitations est aussi importante que la présentation des capacités. En mars 2026, FLIN ne peut pas :

Interopérer avec les bibliothèques JavaScript. Si vous avez besoin de Chart.js, D3 ou d'un composant React spécifique, FLIN ne peut pas l'importer. FLIN a 180 composants d'interface embarqués et en ajoute davantage, mais l'étendue de l'écosystème JavaScript n'est pas répliquée.

Exprimer du SQL complexe. L'API de requête de FLIN couvre les cas courants -- filtrage, tri, pagination, jointures via les relations d'entités -- mais n'expose pas de SQL brut. Si votre application nécessite des fonctions de fenêtre, des CTE ou des agrégations complexes, le langage de requête de FlinDB peut ne pas être suffisant.

Déployer sur des plateformes serverless. FLIN produit un binaire unique qui fait tourner un serveur HTTP. Il ne compile pas vers des fonctions serverless pour AWS Lambda ou Cloudflare Workers. Le modèle de déploiement est un processus long-running, pas une fonction requête-réponse.

Gérer une échelle massive. FlinDB est une base de données embarquée optimisée pour les applications mono-serveur. Si votre application doit gérer des millions d'utilisateurs simultanés à travers un cluster distribué, FLIN n'est pas le bon outil aujourd'hui.

Ces limitations sont connues, documentées et dans certains cas intentionnelles. FLIN est conçu pour une classe spécifique d'applications -- des applications web autonomes construites par de petites équipes -- et il sert cette classe exceptionnellement bien.

Construire votre première application FLIN

Si les exemples de cet article vous ont convaincu d'essayer FLIN, le processus est direct :

bash# Télécharger le binaire FLIN
curl -sSL https://flin.dev/install | sh

# Créer votre première application
mkdir myapp && cd myapp
echo 'count = 0\n\n<button click={count++}>{count}</button>' > app.flin

# La lancer
flin dev

Pas de npm install. Pas de package.json. Pas de fichiers de configuration. La commande flin dev démarre un serveur de développement avec rechargement à chaud, ouvre votre navigateur, et vous construisez.


Prochain dans la série : La feuille de route vers FLIN v1.0 -- 3 452 tests passent, 409 fonctions intégrées fonctionnent, et voici tout ce qui reste avant la version stable.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles