v1.0.0

Build scalable web apps that LLM's can understand and you can trust.

Gleam's type safety and functional core, plus Glimr's opinionated design mean fewer bugs, confident refactoring, and AI that actually writes correct code.

Beautiful Code

A delightful coding experience.

Clean, expressive, and intentional by default. Glimr helps you write code you're happy to come back to whether you're a team of 1 or 100.

user_controller.gleam
/// @get "/users"
pub fn index(ctx: Context(App)) -> Response {
  let users = user.list_or_fail(ctx.app.db)

  response.html(user_index.render(users: users), 200)
}

/// @get "/users/:user_id"
pub fn show(ctx: Context(App), user_id: Int) -> Response {
  let user = user.find_or_fail(ctx.app.db, user_id)

  response.html(user_show.render(user: user), 200)
}
import app/app.{type App}
import glimr/http/context.{type Context}
import glimr/http/http.{type Response}
import glimr/http/kernel.{type Next}

pub fn run(ctx: Context(App), next: Next(App)) -> Response {
  io.println("Request: " <> ctx.req.path)

  // Pass context to next middleware/handler
  next(ctx)
}
import app/http/middleware/logger
import glimr/http/middleware

/// @get "/users"
pub fn index(ctx: Context(App)) -> Response {
  use ctx <- middleware.apply([logger.run], ctx)

  let users = user.list_or_fail(ctx.app.db)
  response.html(user_index.render(users: users), 200)
}
---
props(count: Int)
---

<div class="text-center">
  <p class="text-2xl mb-4">Count: {{ count }}</p>

  <button l-on:click="count = count - 1">-</button>
  <button l-on:click="count = count + 1">+</button>
</div>

<!-- Two-way binding -->
<input l-model="name" />
<p>Hello, {{ name }}!</p>
import compiled/loom/counter

/// @get "/counter"
pub fn show(ctx: Context(App)) -> Response {
  response.html(counter.render(count: 0), 200)
}
/// Define the shape of the data returned after validation
pub type Data {
  Data(
    name: String,
    email: String,
  )
}

/// Define your form's validation rules
fn rules(_ctx: Context(App)) -> List(Rule) {
  [
    v.for("name", [v.Required, v.MinLength(2)]),
    v.for("email", [v.Required, v.Email, v.MaxLength(255)]),
  ]
}
/// @post "/users"
pub fn store(ctx: Context(App)) -> Response {
  use data <- user_store.validate(ctx)

  let user = user.create(
    pool: ctx.app.db, 
    name: data.name, 
    email: data.email
  )

  redirect.to("/users/" <> user.id)
}
pub const table_name = "users"

pub fn definition() {
  schema.table(table_name, [
    schema.id(),
    schema.string("email"),
    schema.string("name") |> schema.nullable(),
    schema.boolean("is_admin") |> schema.default_bool(False),
    schema.unix_timestamps(),
  ])
  |> schema.indexes([
    schema.unique(["email"]),
  ])
}
-- Auto-generated by ./glimr db_gen

CREATE TABLE users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  email TEXT NOT NULL,
  name TEXT,
  is_admin INTEGER NOT NULL DEFAULT 0,
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL
);

CREATE UNIQUE INDEX idx_users_email
  ON users (email);
-- queries/find.sql
SELECT *
FROM users
WHERE id = $1;
-- queries/list_active.sql
SELECT *
FROM users
WHERE is_active = true
ORDER BY created_at DESC;
/// @get "/users/:user_id"
pub fn show(ctx: Context(App), user_id: Int) -> Response {
  let user = user.find_or_fail(ctx.app.db, user_id)

  response.html(user_show.render(user: user), 200)
}

/// @get "/users/active"
pub fn active(ctx: Context(App)) -> Response {
  let users = user.list_active_or_fail(ctx.app.db)

  response.html(user_index.render(users: users), 200)
}
/// @get "/dashboard"
pub fn show(ctx: Context(App)) -> Response {
  // Ensure only authenticated ones reach this route
  use ctx <- middleware.apply([auth_user], ctx)

  // Get the authenticated user from our context
  let assert option.Some(user) = ctx.app.user

  // Pass the authenticated user to your view
  response.html(dashboard.render(user: user), 200)
}
---
import database/main/models/user/gen/user.{type User}

props(user: User) 
---

<x-layouts:app>
  <div class="max-w-2xl mx-auto py-12">
    <h1 class="text-2xl font-bold">
      Welcome back, {{ user.name }}
    </h1>

    <p class="mt-2">{{ user.email }}</p>
  </div>
</x-layouts:app>
user_controller.gleam
/// @get "/users/:user_id"
pub fn show(ctx: Context(App), user_id: Int) -> Response {
  // Fetch from cache, or query DB on miss
  let user = {
    use <- cache.remember_json(
      ctx.app.cache,
      "user:" <> int.to_string(user_id),
      3600,
      user.decoder(),
      user.encoder(),
    )

    user.find_or_fail(ctx.app.db, user_id)
  }

  response.html(user_show.render(user: user), 200)
}

Annotation-based routing

Define routes with simple annotations in your controllers. Glimr compiles them into a pattern-matched router, not a runtime lookup table. The result is extremely fast routing with zero overhead.

Apply middleware at the controller or route level. Routes recompile automatically when you save, so there's no manual step between writing a handler and testing it.

Learn more

Composable middleware

Intercept requests before they reach your controllers. Modify context, check auth, log requests, rate limit, or run any custom logic.

Apply per-route with the use syntax, or per-controller with a middleware() function. Changes to context flow through to all downstream handlers.

Learn more

Reactive templates

Loom templates compile to type-safe Gleam. Inspired by Pheonix LiveView, add event handlers and they become reactive over WebSockets. No JavaScript required.

All state lives on the server. Interactions trigger a minimal diff that patches only what changed. Built-in SPA navigation, two-way binding, loading states, and components with slots.

Learn more

Declarative validation

Define rules declaratively and get back a typed struct, not a bag of strings. Validation errors are handled automatically: flashed to the session for HTML routes, returned as JSON for APIs.

The use syntax short-circuits on failure, so your controller only runs when data is valid. Transform and sanitize inputs in one place before they reach your business logic.

Learn more

Automatic migrations

Define your schema in Gleam with a fluent, type-safe API. Glimr compares it against a stored snapshot, detects changes, and generates driver-specific SQL migrations automatically. Just run ./glimr db_gen and you're done.

Learn more

SQL over ORM

Write real SQL in plain .sql files. Glimr automatically generates fully typed Gleam repository functions you can use directly in your controllers. No ORM magic, no query builders, just write plain SQL like God intended.

Need multiple databases? Glimr supports multiple connections out of the box. SQLite, PostgreSQL, or both. Each with its own models, migrations, and generated repositories.

Learn more

Caching

Built-in caching with file, Redis, SQLite, and PostgreSQL stores. The remember pattern fetches from cache or computes on miss in a single expression.

Cache strings, JSON, or structured data with auto-generated encoders and decoders. Supports TTL, increment/decrement for rate limiting, and flush operations.

Learn more

Built-in auth

Run ./glimr make_auth user and get a complete auth stack: model, migrations, login/register controllers, validators, middleware, and Loom views. All generated, all type-safe.

Need separate auth for admins and users? Use scoped mode for fully independent auth stacks with their own middleware, sessions, and routes.

Learn more
Truly AI-ready

Other frameworks claim to be a good fit for AI agents, here's why Glimr actually is.

Most frameworks were designed before LLM agents wrote code. Gleam and Glimr have the constraints that make an agent-driven workflow actually reliable.

Tight feedback loop

Gleam's strict type system and compiler catch errors instantly. When an LLM generates wrong code, it gets precise error messages it can act on, not silent failures that surface in production.

Functional over OOP

Gleam is a functional language, meaning no inheritance, no decorators, no implicit behavior. Everything in Gleam is explicit and in the code. An LLM doesn't need context it can't see.

Convention over configuration

Glimr's opinionated conventions mean the LLM doesn't have to choose between ORMs, routers, or project structures. There's one proven path, and it's well-documented.

Frameworks like Laravel and Rails pioneered convention over configuration, but they're built on dynamic, object-oriented languages. An LLM can generate code that looks right, passes linting, and breaks in production. Gleam's compiler won't let that happen. Functions take data in and return data out. Conventions tell the LLM what to write. The type system tells it what it got wrong.

Batteries included

Ship with confidence, not dependencies.

Glimr is still young, but comes with a lot out of the box. Routing, auth, validation, caching, and more. No need to assemble a stack from scattered packages.

Middleware

Apply middleware per-route or per-controller. Compose auth, rate limiting, logging, and custom logic in any order.

Route groups

Organize routes into web and API groups with their own middleware stacks. Add custom groups as your app grows.

Lightning fast routing

Routes compile to pattern-matched Gleam code on the BEAM runtime. Zero overhead, zero runtime lookups.

PostgreSQL + SQLite

First-class support for both databases. Write SQL, and Glimr generates fully typed repository functions and driver-specific migrations automatically.

Redis + file cache

Built-in caching with Redis, file, SQLite, and PostgreSQL stores. The remember pattern handles cache-or-compute in a single expression.

Multi database

Run multiple database connections side by side. Each with its own models, migrations, queries, and generated repositories.

Auth scaffolding

One command generates a full auth stack: model, controllers, validators, middleware, and views. Scoped mode for multi-model auth.

Live views

Server-driven reactivity over WebSockets. SPA navigation, two-way binding, loading states, and components with slots. No JS required.

Form validation

Declarative rules that return typed structs. Automatic error handling for HTML and JSON responses. Sanitize inputs in one place.

Join Newsletter

Keep up with Glimr's development

Enter your email and stay up to date on new features, releases, and tips for building with Glimr. No spam, unsubscribe anytime.

This website is open-source and built with Glimr!