244 lines
8.9 KiB
Markdown
244 lines
8.9 KiB
Markdown
|
|
# IHP Framework Overview
|
|||
|
|
|
|||
|
|
> Reference notes for implementing the Interaction Hub Framework (IHF) using IHP.
|
|||
|
|
> Based on IHP v1.5.0 (released March 2026).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## What IHP Is
|
|||
|
|
|
|||
|
|
**IHP** (Integrated Haskell Platform) is a batteries-included, full-stack web framework built on Haskell and Nix. Its goal is rapid application development with robust, type-safe code.
|
|||
|
|
|
|||
|
|
- **Language:** Haskell (GHC 9.10 default; GHC 9.12 experimental in v1.5)
|
|||
|
|
- **Paradigm:** Functional, strongly typed, server-rendered with optional realtime
|
|||
|
|
- **Creator:** [digitally induced](https://github.com/digitallyinduced) (Hamburg). Open-sourced 2020, in production since 2017
|
|||
|
|
- **Current version:** v1.5.0 (March 25, 2026) — largest release to date (1,051 commits)
|
|||
|
|
- **License:** MIT
|
|||
|
|
|
|||
|
|
### v1.5 Headline Changes
|
|||
|
|
|
|||
|
|
| Change | Impact |
|
|||
|
|
|--------|--------|
|
|||
|
|
| `postgresql-simple` → `hasql` driver | Up to 50% lower query latency |
|
|||
|
|
| Dev server RAM: 4 GB → 500–800 MB | Practical on smaller machines |
|
|||
|
|
| Session middleware 3×, URL gen 5×, rendering 2× faster | Overall snappier |
|
|||
|
|
| `typedSql` quasiquoter | Compile-time SQL type checking against live dev DB |
|
|||
|
|
| `fetchPipelined` | Multiple queries in one network round-trip |
|
|||
|
|
| Composite primary key support | Needed for join-table models |
|
|||
|
|
| Integration test mode | Temporary Postgres DB per test run |
|
|||
|
|
| 15+ modules on Hackage separately | `ihp-mail`, `ihp-datasync`, etc. |
|
|||
|
|
|
|||
|
|
### Design Philosophy
|
|||
|
|
|
|||
|
|
- Type errors at compile time, not runtime
|
|||
|
|
- Single command (`devenv up`) starts a fully self-contained environment — Postgres included, managed by Nix. No Docker, no Kubernetes required
|
|||
|
|
- Optimized for AI-assisted development — the type system automatically verifies generated code
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Core Architecture
|
|||
|
|
|
|||
|
|
### MVC-Influenced Structure
|
|||
|
|
|
|||
|
|
| Layer | IHP Location | Role |
|
|||
|
|
|-------|-------------|------|
|
|||
|
|
| Model | `Application/Schema.sql` + generated types | Schema, query builder, relationships |
|
|||
|
|
| Controller | `Web/Controller/<Name>.hs` | Action handlers, parameter binding, DB calls |
|
|||
|
|
| View | `Web/View/<Name>/<Action>.hs` | HSX templates, `View` typeclass |
|
|||
|
|
| Routing | `Web/Routes.hs` + `Web/FrontController.hs` | URL ↔ action mapping |
|
|||
|
|
| Types | `Web/Types.hs` | All controller action constructors |
|
|||
|
|
| Helpers | `Application/Helper/Controller.hs`, `Application/Helper/View.hs` | Shared logic |
|
|||
|
|
|
|||
|
|
Multi-application support: a single project can contain multiple sub-apps (`Web/`, `Admin/`). `new-application admin` generates an `Admin/` subtree with routes auto-prefixed `/admin/`.
|
|||
|
|
|
|||
|
|
### Type-Safe URL / Action System
|
|||
|
|
|
|||
|
|
The IHP router always maps HTTP requests to **data constructors** defined in `Web/Types.hs`:
|
|||
|
|
|
|||
|
|
```haskell
|
|||
|
|
data WidgetsController
|
|||
|
|
= WidgetsAction
|
|||
|
|
| NewWidgetAction
|
|||
|
|
| ShowWidgetAction { widgetId :: !(Id Widget) }
|
|||
|
|
| CreateWidgetAction
|
|||
|
|
| EditWidgetAction { widgetId :: !(Id Widget) }
|
|||
|
|
| UpdateWidgetAction { widgetId :: !(Id Widget) }
|
|||
|
|
| DeleteWidgetAction { widgetId :: !(Id Widget) }
|
|||
|
|
deriving (Eq, Show, Data)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- URLs generated from values, not strings: `pathTo ShowWidgetAction { widgetId = someId }`
|
|||
|
|
- Compile-time guarantee: broken links are type errors, not 404s
|
|||
|
|
- `urlTo` generates absolute URLs (protocol + domain)
|
|||
|
|
|
|||
|
|
### Routing
|
|||
|
|
|
|||
|
|
Defined in `Web/Routes.hs`, registered in `Web/FrontController.hs`.
|
|||
|
|
|
|||
|
|
**AutoRoute** (most common): `instance AutoRoute WidgetsController` — IHP generates RESTful routes from action name prefixes:
|
|||
|
|
|
|||
|
|
| Prefix | HTTP Method |
|
|||
|
|
|--------|------------|
|
|||
|
|
| `Delete` | DELETE |
|
|||
|
|
| `Create`, `Update` | POST/PATCH |
|
|||
|
|
| anything else | GET |
|
|||
|
|
|
|||
|
|
**Custom routes:** implement `CanRoute` (attoparsec parser URL → action) and `HasPath` (reverse). `customRoutes` overrides individual AutoRoute entries. Supports SEO slugs like `/widgets/my-slug`.
|
|||
|
|
|
|||
|
|
HTTP method override for HTML forms: pass `_method=DELETE` (or `PATCH`) as a hidden field.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Development Workflow
|
|||
|
|
|
|||
|
|
### Installation
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 1. Install Determinate Nix (with Flakes + lazy-trees)
|
|||
|
|
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
|||
|
|
|
|||
|
|
# 2. Install ihp-new
|
|||
|
|
nix profile install nixpkgs#ihp-new
|
|||
|
|
|
|||
|
|
# 3. Install direnv and hook into shell
|
|||
|
|
nix profile add nixpkgs#direnv
|
|||
|
|
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc # or zshrc
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Creating a Project
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ihp-new my-project
|
|||
|
|
cd my-project
|
|||
|
|
devenv up
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
First startup: 10–15 minutes (downloads GHC, Postgres, all Haskell deps via Nix binary cache). Subsequent starts are fast (under 30s).
|
|||
|
|
|
|||
|
|
### Dev Server
|
|||
|
|
|
|||
|
|
`devenv up` starts everything:
|
|||
|
|
- Application server on `http://localhost:8000`
|
|||
|
|
- IHP IDE + Schema Designer on `http://localhost:8001`
|
|||
|
|
- Postgres (managed by Nix; no system Postgres needed)
|
|||
|
|
- Live reloading (typically sub-50ms after save)
|
|||
|
|
|
|||
|
|
### Project File Structure
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
my-project/
|
|||
|
|
├── Application/
|
|||
|
|
│ ├── Schema.sql # Single source of truth for all DB types
|
|||
|
|
│ ├── Migration/ # <timestamp>-description.sql files
|
|||
|
|
│ ├── Helper/
|
|||
|
|
│ │ ├── Controller.hs # Shared controller helpers
|
|||
|
|
│ │ └── View.hs # Shared view helpers
|
|||
|
|
│ └── Script/ # One-off scripts / cron job binaries
|
|||
|
|
├── Web/
|
|||
|
|
│ ├── Types.hs # ALL controller action constructors
|
|||
|
|
│ ├── Routes.hs # AutoRoute instance declarations
|
|||
|
|
│ ├── FrontController.hs # WAI entry; dispatch; auth init; default layout
|
|||
|
|
│ ├── Controller/ # One file per controller
|
|||
|
|
│ ├── View/ # One dir per controller, one file per action
|
|||
|
|
│ │ └── Layout.hs # Default layout (Html -> Html)
|
|||
|
|
│ └── Component/ # Server-Side Components (optional)
|
|||
|
|
├── Config/
|
|||
|
|
│ ├── Config.hs # Env vars, secrets, feature flags
|
|||
|
|
│ └── nix/
|
|||
|
|
│ └── hosts/
|
|||
|
|
│ └── production/ # Declarative NixOS server config
|
|||
|
|
├── Test/ # Integration tests
|
|||
|
|
├── static/ # CSS, JS, images
|
|||
|
|
├── flake.nix # Nix flake — all deps declared here
|
|||
|
|
├── App.cabal # Cabal package definition
|
|||
|
|
├── Main.hs # Entry point
|
|||
|
|
└── Makefile # Build targets
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Adding Dependencies
|
|||
|
|
|
|||
|
|
In `flake.nix`:
|
|||
|
|
```nix
|
|||
|
|
ihp = {
|
|||
|
|
enable = true;
|
|||
|
|
projectPath = ./.;
|
|||
|
|
packages = [ pkgs.imagemagick ]; # native deps
|
|||
|
|
haskellPackages = p: [
|
|||
|
|
p.ihp
|
|||
|
|
p.aeson
|
|||
|
|
p.your-library
|
|||
|
|
];
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Code Generators
|
|||
|
|
|
|||
|
|
Navigate to `http://localhost:8001/Generators` or right-click a table in Schema Designer → "Generate Controller". Scaffolds controllers, views, routes, and type entries to match the table's fields. Also available: Migration, Job, Mail, Script generators.
|
|||
|
|
|
|||
|
|
### Testing
|
|||
|
|
|
|||
|
|
v1.5: integration test mode creates a temporary Postgres DB automatically per test run. Tests live in `Test/`.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Deployment
|
|||
|
|
|
|||
|
|
### Primary: NixOS / `deploy-to-nixos`
|
|||
|
|
|
|||
|
|
The entire server config (nginx, Let's Encrypt, systemd, app config) lives declaratively in `Config/nix/hosts/production/` — version-controlled and reproducible.
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
deploy-to-nixos production
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Runs `nixos-rebuild` remotely over SSH. AWS EC2: NixOS AMI, `t3a.small` min (`t3a.medium` recommended), 60 GiB root disk, ports 22/80/443.
|
|||
|
|
|
|||
|
|
**Required env vars:**
|
|||
|
|
|
|||
|
|
| Variable | Purpose |
|
|||
|
|
|----------|---------|
|
|||
|
|
| `IHP_SESSION_SECRET` | Session encryption key (`new-session-secret` to generate) |
|
|||
|
|
| `DATABASE_URL` | Postgres connection string |
|
|||
|
|
| `IHP_BASEURL` | External URL (e.g., `https://example.com`) |
|
|||
|
|
|
|||
|
|
**Production features built-in:**
|
|||
|
|
- Systemd watchdog (heartbeat 30s; auto-restart after 60s)
|
|||
|
|
- Socket activation (queues requests during restarts — zero-downtime deploys)
|
|||
|
|
- Sentry integration via `ihp-sentry` (IHP Pro)
|
|||
|
|
|
|||
|
|
### Docker (IHP Pro)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
nix build .#unoptimized-docker-image --option sandbox false
|
|||
|
|
cat result | podman load
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Env vars: same as above + `IHP_ASSET_VERSION` (cache-busting) + `IHP_REQUEST_LOGGER_IP_ADDR_SOURCE=FromHeader` (behind load balancer). Minimal Docker images lack CA certificates — copy `caCertificates` and set `SSL_CERT_FILE`.
|
|||
|
|
|
|||
|
|
### Bare Metal Binary
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
nix build .#optimized-prod-server # full optimisation
|
|||
|
|
nix build .#unoptimized-prod-server # faster build, for staging
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Runtime: `IHP_ENV=Production`, `DATABASE_URL`, `PORT` (default 8000). GHC RTS tunable via `GHCRTS`.
|
|||
|
|
|
|||
|
|
### CSS/JS Bundling
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
make static/prod.js static/prod.css
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Dev uses individual files; production uses bundled/minified versions. Background jobs require deploying the `RunJobs` binary alongside the main app.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Key Links
|
|||
|
|
|
|||
|
|
- [IHP Homepage](https://ihp.digitallyinduced.com/)
|
|||
|
|
- [IHP Guide (full docs)](https://ihp.digitallyinduced.com/Guide/)
|
|||
|
|
- [GitHub: digitallyinduced/ihp](https://github.com/digitallyinduced/ihp)
|
|||
|
|
- [ihp-boilerplate](https://github.com/digitallyinduced/ihp-boilerplate) — template used by `ihp-new`
|
|||
|
|
- [v1.5 release announcement](https://discourse.haskell.org/t/ihp-v1-5-has-been-released/13846)
|