Introduction
IHP provides a few basic functions to access the database. On top of Postgres SQL, we try to provide a thin layer to make it easy to do all the common tasks your web application usually does. Haskell data structures and types are generated automatically based on your database schema. The only supported database platform is Postgres. Focusing on Postgres allows us to better integrate advanced Postgres-specific solutions into your application. In development, you do not need to set up anything to use Postgres. The built-in development server automatically starts a Postgres instance to work with your application. The built-in development Postgres server is only listening on a Unix socket and is not available via TCP.Connecting to the Database
Via Terminal
When the development server is running, you can connect to it viapostgresql:///app?host=YOUR_PROJECT_DIRECTORY/build/db with your favorite database tool.
When inside the project directory you can also use:
psql by pointing at the local sockets file:
Via UI Tools
When the development server is running, you can use your favorite UI tool (e.g. TablePlus) that allows connecting to Postgres.Get the Database Host
This is the application root + “/build/db”. Use this command on terminal from the root of your app and copy the output:
Get the Database Username
This is the current user you run the terminal with. Run
whoami command to get that name.Schema.sql
Once you have created your project, the first step is to define a database schema. The database schema is a SQL file with a lot ofCREATE TABLE ... statements. You can find it at Application/Schema.sql.
In a new project, this file will be empty.
If you are using PostgreSQL 18 or newer, you can set the environment variable
IHP_POSTGRES_VERSION=18 to use the native uuidv7() function as the default for new tables and jobs instead of uuid_generate_v4(). UUIDv7 provides time-ordered UUIDs that are better for database indexing.CREATE TABLE ... statements to the Schema.sql. For a users table this can look like this:
Schema.sql file. They are re-generated on every file change of the Schema.sql. We use the well-known postgresql-simple Haskell library to connect to the database.
Haskell Bindings
Model Context
In a pure functional programming language like Haskell, we need to pass the database connection to all functions which need to access the database. We use an implicit parameter?modelContext :: ModelContext to pass around the database connection without always specifying it. The ModelContext data structure is basically just a wrapper around the actual database connection.
An implicit parameter is a parameter which is automatically passed to certain functions, it just needs to be available in the current scope.
This means that all functions which are running database queries will need to be called from a function which has this implicit parameter in scope. A function doing something with the database, will always have a type signature specifying that it requires the ?modelContext to be available, like this:
?modelContext in scope and thus can run database queries. Other application entry-points, like e.g. Scripts, also have this in scope.
This also means, that when a function does not specify that it depends on the database connection in its type signature (like ?modelContext :: ModelContext => ..), you can be sure that it’s not doing any database operations.
Haskell Data Structures
For every table in theSchema.sql a corresponding data structure will be generated on the Haskell side. For example, given a table:
id field type Id User is basically just a wrapper around UUID for type-safety reasons. All database field names are mapped from under_score to camelCase on the Haskell side.
When a SQL field can be NULL, the Haskell field type will be contained in Maybe.
In the Schema Designer, you can take a look at the generated Haskell code by right-clicking the table and clicking
Show Generated Haskell Code.Enums
It’s possible to define and use custom enum types with IHP. An enum can be created using the Schema Designer. The process is pretty much the same as when creating a table.Adding Enums via the Schema Designer
Open the Schema Designer, right-click into theObjects Pane, and then select Add Enum.
You have to give a name to your enum type. The name should be in plural form, like with table names. E.g. we could name our enum colors.
Next, add the enum values by right-clicking into the Values pane and click on Add Value. Here we could add values such as red, blue and yellow.
Adding Enums via SQL
Instead of using the Schema Designer, you can also just add the required SQL statement manually intoApplication/Schema.sql:
Using Enums in Haskell Code
The abovecolors example will allow us to access the enum like this:
posts table and there give each post a color:
fill even with custom enums:
inputValue to get a textual representation for your enum which works with fill:
Supported Database Types
IHP currently has support for the following Postgres column types:- UUID
- Text
- VARCHAR(..), CHARACTER VARYING(..)
- TIMESTAMP WITHOUT TIMEZONE, TIMESTAMP
- TIMESTAMP WITH TIMEZONE, TIMESTAMPZ
- BIGINT, INT8
- SMALLINT, INT2
- INTEGER, INT4, INT
- BOOLEAN, BOOL
- REAL, FLOAT4
- DOUBLE PRECISION, FLOAT8
- POINT
- DATE
- BYTEA
- TIME
- NUMERIC, NUMERIC(..)
- CHAR(..), CHARACTER(..)
- SERIAL
- BIGSERIAL
- JSONB
- INET (Only IP addresses, CIDR not supported yet)
- TSVECTOR
- Arrays of all the above types
- Custom types, usually enums
Transactions
You can use thewithTransaction function to wrap your database operations in a Postgres database transaction:
withTransaction function will automatically commit after it successfully executed the passed do-block. When any exception is thrown, it will automatically rollback.
Unique Constraints
It’s possible to use the UI to set the unique constraint on a column. However, sometimes you might want to add a unique constraint on multiple columns. This can be done by adding a unique constraint to theSchema.sql file. For example, to add a unique constraint on the email and username columns of the users table, you would add the following to the Schema.sql file:
PostgreSQL constraint names have a 63-byte limit.For a multi-column unique constraint, Postgres auto-generates a name from table + column names. For Alternative: use
strategy_factor_regime_online + (symbol_id, timeframe, ts, version), this becomes a long name (truncated form: strategy_factor_regime_online_symbol_id_timeframe_ts_version_ke) and can trigger noisy migrations.Solution: use a short explicit constraint name after CREATE TABLE strategy_factor_regime_online:CREATE UNIQUE INDEX ....