Theory is cheap. Every new language promises simplicity, every new framework claims to solve complexity, and every manifesto reads well until you try to build something real. FLIN has published its vision, its architecture, and its philosophy. Now it is time to show the code.
This article presents six complete FLIN applications, progressing from trivial to substantial: a counter, a hello-world with input binding, a full todo application with persistence, a blog with comments, an API with CRUD endpoints, and a real-time dashboard with temporal queries. Each example is a working program -- not a sketch, not pseudocode, not a simplified excerpt that hides the hard parts.
The goal is to answer the question that every developer asks when confronted with a new language: what does it actually look like when I sit down and build something?
Example 1: The Counter (3 Lines)
Every UI framework starts with a counter. Here is FLIN's:
count = 0```
Three lines. One reactive variable. One HTML element. One event handler. Save this as counter.flin, run flin dev, and the browser opens with a button displaying 0. Click it. The number increments. There is nothing else to configure, nothing else to install, nothing else to understand.
The count = 0 declaration creates a reactive variable. Reactivity is the default in FLIN -- there is no useState, no $state, no signal(). Every variable is tracked by the compiler. When count changes, every view expression that references count re-evaluates automatically.
The click={count++} attribute is an inline event handler. No onClick, no on:click, no event delegation setup. The attribute name matches the DOM event name, and the value is an expression that executes when the event fires.
The {count} interpolation renders the current value. When count changes, this text node updates. No virtual DOM diffing for a single text node -- the compiler knows exactly which DOM node to update because it tracks the dependency at compile time.
Example 2: Input Binding (5 Lines)
FLIN's two-way binding makes form handling effortless:
name = ""Hello, {if name then name else "World"}!
The value={name} attribute creates a two-way binding. When the user types into the input, name updates. When name updates, the content re-renders. The ternary-style if/then/else expression handles the empty state inline.
Compare this with React, where you would need useState, an onChange handler that calls setName(e.target.value), and a controlled component pattern that many beginners find confusing. In FLIN, the binding is implicit in the syntax. You assign a variable to value, and the runtime handles synchronization in both directions.
Example 3: The Todo App (43 Lines)
The todo application is the "hello world" of frontend frameworks. It tests persistence, list rendering, filtering, event handling, and conditional styling. Here is the complete FLIN implementation:
todos = []
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) }
My Todos
{for todo in filtered}
```
Let us walk through the key constructs.
The entity keyword. entity Todo { ... } declares a persistent data type. The FLIN compiler generates a database table in FlinDB, registers query methods (Todo.all, Todo.where(...), Todo.count), and enables temporal tracking. The = false and = now are default values -- done defaults to false, created defaults to the current timestamp.
The match expression. This is FLIN's pattern matching construct. Based on the value of filter, it selects the appropriate database query. Todo.all returns every todo. Todo.where(done == false) generates a filtered query. Because filter is reactive, changing it re-evaluates the match expression, which re-runs the query, which updates the view.
The save keyword. save Todo { title: newTodo } creates a new Todo record in the database. save todo (without a type constructor) updates an existing record. One keyword for both create and update -- the runtime checks whether the entity has an ID to determine which operation to perform.
The delete keyword. delete todo removes the record from the database and triggers a reactive update that removes it from the view.
The enter event. FLIN adds semantic event names beyond the standard DOM events. enter fires when the user presses the Enter key in an input field. No onKeyDown handler checking event.key === 'Enter'.
The entire application -- UI, database, filtering, CRUD operations -- lives in a single file with zero imports, zero configuration, and zero external dependencies.
Example 4: A Blog With Comments (65 Lines)
A blog exercises relationships between entities, file-based routing, date formatting, and content rendering. Here is the home page:
entity 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")
My Blog
{for post in posts}
{post.content.slice(0, 200)}...{post.title}
And the individual post page, saved as post/[slug].flin:
post = Post.where(slug == params.slug).first
comments = Comment.where(post.id == post.id)
newComment = ""
author = ""{if post}
{post.title}
{for comment in comments}
{comment.content}
{comment.created.from_now}{else}
Post not found
{/if} ```Several new concepts appear here.
Semantic text. The content: semantic text type annotation tells FLIN to automatically generate vector embeddings for this field, enabling semantic search across post content. This is not a separate service -- FlinDB handles embedding and vector storage natively.
Entity relationships. post: Post in the Comment entity creates a foreign key relationship. FLIN handles the constraint, the join, and the cascade delete automatically.
File-based routing. The filename post/[slug].flin creates a dynamic route. The params.slug variable is automatically populated from the URL. This is the same pattern used by Next.js and SvelteKit, but without a framework -- it is built into the language.
Temporal formatting. post.created.format("MMMM D, YYYY") and comment.created.from_now are built-in methods on the time type. No Moment.js. No date-fns. No importing a formatting library.
Example 5: API Endpoints (30 Lines)
FLIN is not just a frontend language. It handles HTTP API endpoints with the same file-based routing approach:
// 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 } ```
// 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 } } ```
The route keyword declares an HTTP method handler. body is the parsed request body (JSON). params provides URL parameters. The return value is automatically serialized to JSON with the appropriate status code -- 200 for successful reads, 201 for creates, 404 when User.find returns none.
In an Express.js application, this same API would require: Express itself, a body parser middleware, a router, a database driver, an ORM or query builder, error handling middleware, and validation logic. The FLIN version is thirty lines across two files with zero dependencies.
Example 6: A Dashboard With Time Travel (35 Lines)
FLIN's temporal database enables queries that most languages cannot express without a dedicated time-series database. Here is a metrics dashboard that compares current values with historical ones:
entity 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
Revenue
{revenue_today.value.format()} 0 then "up" else "down"}> {change.to_fixed(1)}%
Revenue History
{for version in revenue_today.history.last(7)}The @ operator is FLIN's temporal query syntax. revenue_today @ yesterday retrieves the state of the revenue metric as it existed yesterday. This is not a second database query to a separate time-series store -- FlinDB maintains the complete history of every entity automatically.
revenue_today.history returns the full version history of the record. .last(7) takes the seven most recent versions. The entire history feature requires zero additional configuration -- every entity in FLIN has temporal tracking enabled by default.
In a traditional stack, building this dashboard would require: a time-series database (InfluxDB, TimescaleDB, or a custom audit log table), a cron job or trigger to snapshot values, a backend endpoint to compute deltas, and frontend state management to combine current and historical data. FLIN collapses all of this into the @ operator and the .history property.
The Patterns
After six examples, certain patterns emerge that define FLIN's approach to application development.
One file, one concern. Each .flin file is a self-contained unit -- a page, an API endpoint, or a component. There is no separation of "frontend" and "backend." The file declares its entities, its queries, its event handlers, and its view.
Reactivity is invisible. Variables are reactive by default. The developer never thinks about state management, subscriptions, or re-rendering. The compiler tracks dependencies and updates the view automatically.
Persistence is a keyword. save and delete are language-level operations, not library calls. The developer does not think about database connections, transactions, or serialization.
Queries are method chains. Entity.where(...), Entity.all, Entity.find(...), Entity.count -- database queries read like English. No SQL strings, no query builders, no ORM configuration.
Time is a first-class concept. Every entity has history. The @ operator queries past states. The .history property returns the full version log. Time-travel is not an advanced feature -- it is how FLIN works.
The Compilation Behind the Simplicity
Each of these examples compiles through FLIN's four-phase pipeline. Here is what happens when the compiler processes the todo app:
Phase 1: Lexical Analysis. The lexer reads the source file character by character and produces a stream of tokens. The entity keyword becomes TokenKind::Keyword(Keyword::Entity). The {for opening becomes a view control token. The tag becomes a TagOpen token followed by an identifier.
Phase 2: Syntactic Analysis. The parser builds an abstract syntax tree from the token stream. The entity Todo { ... } block becomes an EntityDeclaration node. The block becomes a ViewElement node with child nodes for each HTML element and control flow block.
Phase 3: Semantic Analysis. The type checker validates the AST. It verifies that todo.done is a boolean (so !todo.done is valid), that Todo.where(done == false) returns a list of Todos (so {for todo in filtered} is correctly typed), and that save Todo { title: newTodo } provides all required fields.
Phase 4: Code Generation. The code generator emits bytecode instructions for the FLIN virtual machine. Entity declarations become schema registration instructions. View elements become DOM construction instructions. Reactive bindings become dependency tracking instructions. Database operations become FlinDB API calls.
The developer writes 43 lines. The compiler produces hundreds of bytecode instructions. The runtime executes them, managing reactivity, persistence, and rendering behind the scenes.
What You Cannot Do (Yet)
Honesty about limitations is as important as showcasing capabilities. As of March 2026, FLIN cannot:
Interop with JavaScript libraries. If you need Chart.js, D3, or a specific React component, FLIN cannot import it. FLIN has 180 embedded UI components and is adding more, but the JavaScript ecosystem's breadth is not replicated.
Express complex SQL. FLIN's query API covers the common cases -- filtering, ordering, pagination, joins via entity relationships -- but does not expose raw SQL. If your application requires window functions, CTEs, or complex aggregations, FlinDB's query language may not be sufficient.
Deploy to serverless platforms. FLIN produces a single binary that runs an HTTP server. It does not compile to serverless functions for AWS Lambda or Cloudflare Workers. The deployment model is a long-running process, not a request-response function.
Handle massive scale. FlinDB is an embedded database optimized for single-server applications. If your application needs to handle millions of concurrent users across a distributed cluster, FLIN is not the right tool today.
These limitations are known, documented, and in some cases intentional. FLIN is designed for a specific class of applications -- self-contained web applications built by small teams -- and it serves that class exceptionally well.
Building Your First FLIN App
If the examples in this article have convinced you to try FLIN, the process is straightforward:
# Download the FLIN binary
curl -sSL https://flin.dev/install | sh# Create your first app mkdir myapp && cd myapp echo 'count = 0\n\n' > app.flin
# Run it flin dev ```
No npm install. No package.json. No configuration files. The flin dev command starts a development server with hot reloading, opens your browser, and you are building.
---
Next in the series: The Roadmap to FLIN v1.0 -- 3,452 tests pass, 409 built-in functions work, and here is everything that remains before the stable release.
Comments ({comments.count})