π₯ The fastest way to build type safe web apps. IHP is a new batteries-included web framework optimized for longterm productivity and programmer happiness
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
This release brings a new out of the box deployment process based on nixos-rebuild, a new way to get docker images for an IHP app and much more.
Deployment with deploy-to-nixos
:
You can now easily deploy IHP apps to any NixOS server that is reachable via SSH. E.g. if you start a new AWS EC2 instance with an NixOS image and your SSH key, you can now deploy the IHP app, including database and migrations within minutes.
The flake.nix
can export a full NixOS configuration like this:
{
inputs = {
ihp.url = "github:digitallyinduced/ihp/v1.2";
nixpkgs.follows = "ihp/nixpkgs";
flake-parts.follows = "ihp/flake-parts";
devenv.follows = "ihp/devenv";
systems.follows = "ihp/systems";
};
outputs = inputs@{ ihp, flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp.flakeModules.default ];
perSystem = { pkgs, ... }: {
ihp = {
enable = true;
projectPath = ./.;
packages = with pkgs; [
# Native dependencies, e.g. imagemagick
nodejs
];
haskellPackages = p: with p; [
# Haskell dependencies go here
p.ihp
cabal-install
base
wai
text
hlint
http-streams
ihp-stripe
ihp-oauth-google
retry
];
};
};
flake.nixosConfigurations."staging.example.com" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = inputs;
modules = [
"${nixpkgs}/nixos/modules/virtualisation/amazon-image.nix"
ihp.nixosModules.appWithPostgres
({ ... }: {
security.acme.defaults.email = "[email protected]";
services.ihp = {
domain = "myihpapp.com";
migrations = ./Application/Migration;
schema = ./Application/Schema.sql;
fixtures = ./Application/Fixtures.sql;
sessionSecret = "xxx";
additionalEnvVars = {
GHCRTS = "-A32M -N2";
};
};
# This should reflect the nixos version from the NixOS AMI initally installed
# After the initial install, it should not be changed. Otherwise e.g. the postgres
# server might need a manual data migration if NixOS changes the default postgres version
system.stateVersion = "23.05";
# Optional Example: Email on App Crash
systemd.services.app.onFailure = [ "notify-email@%n.service" ];
systemd.services.worker.onFailure = [ "notify-email@%n.service" ];
programs.msmtp = {
enable = true;
defaults = {
tls = true;
port = 587;
};
accounts = {
default = {
auth = true;
from = "[email protected]";
host = "email-smtp.eu-west-1.amazonaws.com";
user = "XXXXXXXX";
passwordeval = "echo 'XXXXXXXX'";
};
};
};
systemd.services."notify-email@" = {
serviceConfig.Type = "oneshot";
path = with pkgs; [ systemd system-sendmail ];
scriptArgs = "%I";
script = ''
UNIT=$(systemd-escape $1)
TO="[email protected]"
SUBJECT="$UNIT Failed"
HEADERS="To:$TO\nSubject: $SUBJECT\n"
BODY=$(systemctl status --no-pager $UNIT || true)
echo -e "$HEADERS\n$BODY" | sendmail -t
'';
};
# Optional Example: Run an IHP script every 30mins
systemd.services.monitorCampaigns = {
serviceConfig = {
Type = "oneshot";
WorkingDirectory = "${ihpApp}/lib";
ExecStart = "${ihpApp}/bin/MonitorCampaigns";
};
environment = config.systemd.services.app.environment;
onFailure = [ "notify-email@%n.service" ];
};
systemd.timers.monitorCampaignsEvery30Mins = {
wantedBy = [ "timers.target" ];
partOf = [ "monitorCampaigns.service" ];
timerConfig = {
OnCalendar = "*-*-* *:30:00";
Unit = "monitorCampaigns.service";
};
};
})
];
};
};
}
Assuming your NixOS server can be conneted to via ssh staging.example.com
, you can now run this:
deploy-to-nixos staging.example.com
If you use an external postgres (this is likely the case for most serious production deployments), use ihp.nixosModules.app
instead of ihp.nixosModules.appWithPostgres
.
This will now apply the full above NixOS configuration to the server. Internally this tool is a wrapper around nixos-rebuild
. E.g. the above call with result in:
nixos-rebuild switch -j auto --use-substitutes --fast --flake .#staging.example.com --target-host staging.example.com --build-host staging.example.com --option substituters https://digitallyinduced.cachix.org --option trusted-public-keys digitallyinduced.cachix.org:digitallyinduced.cachix.org-1:y+wQvrnxQ+PdEsCt91rmvv39qRCYzEgGQaldK26hCKE=
ssh staging.example.com systemctl start migrate
If you e.g. want to build the binaries on a different server than your runtime server, you can call nixos-rebuild
directly instead of using the deploy-to-nixos
wrapper.
IHP now ships serveral NixOS modules that you can use to compose your IHP NixOS infrastructure.
Docker Images: You can now build docker images from your IHP apps with ease:
# Faster build times, but unoptimized GHC binaries
nix build .#unoptimized-docker-image
# Slow build times, but optimized GHC binaries
nix build .#optimized-docker-image
Support HSX expressions like <input value={project.id}/>
You can now use IHP UUIDs/ID values like user ID or project ID in HSX attributes:
-- Previous:
<input value={inputValue project.id}/>
-- New:
<input value={project.id}/>
IHP.EnvVar
module
streamCompletionWithoutRetry
sometimes dropping tokens
Full Changelog: https://github.com/digitallyinduced/ihp/compare/v1.0.1...v1.1.0
Help decide what's coming next to IHP by using the Feature Voting!
β See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP Discourse.
π§ To stay in the loop, subscribe to the IHP release emails (right at the bottom of the page). Or follow digitally induced on twitter.
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
This release brings some large improvements to the dev environment by integrating devenv.sh, adds native GPT4 support through ihp-openai and much more.
devenv.sh x IHP:
IHP projects now use devenv.sh. devenv is a wrapper around nix flakes that provides fast, declarative, reproducable and composable development environments. It supercedes the previous .envrc
approach. Especially projects with lots of dependencies are much faster to open with devenv.
ihp-openai:
The new ihp-openai
package adds an easy way to integrate GPT3 and GPT4 to your Haskell web apps. The library is extracted from a production app at digitally induced. Compared to existing haskell libs this library is a streaming API (so works great with IHP AutoRefresh and IHP DataSync), works with the latest Chat API, and has smart retry on error without throwing away tokens. Also it's battle tested in real world production use cases.
The package can be found in the IHP repo and a demo project can be found on GitHub as well.
Example:
module Web.Controller.Questions where
import Web.Controller.Prelude
import Web.View.Questions.Index
import Web.View.Questions.New
import Web.View.Questions.Edit
import Web.View.Questions.Show
import qualified IHP.OpenAI as GPT
instance Controller QuestionsController where
action QuestionsAction = autoRefresh do
questions <- query @Question
|> orderByDesc #createdAt
|> fetch
render IndexView { .. }
action NewQuestionAction = do
let question = newRecord
|> set #question "What makes haskell so great?"
render NewView { .. }
action CreateQuestionAction = do
let question = newRecord @Question
question
|> fill @'["question"]
|> validateField #question nonEmpty
|> ifValid \case
Left question -> render NewView { .. }
Right question -> do
question <- question |> createRecord
setSuccessMessage "Question created"
fillAnswer question
redirectTo QuestionsAction
action DeleteQuestionAction { questionId } = do
question <- fetch questionId
deleteRecord question
setSuccessMessage "Question deleted"
redirectTo QuestionsAction
fillAnswer :: (?modelContext :: ModelContext) => Question -> IO (Async ())
fillAnswer question = do
-- Put your OpenAI secret key below:
let secretKey = "sk-XXXXXXXX"
-- This should be done with an IHP job worker instead of async
async do
GPT.streamCompletion secretKey (buildCompletionRequest question) (clearAnswer question) (appendToken question)
pure ()
buildCompletionRequest :: Question -> GPT.CompletionRequest
buildCompletionRequest Question { question } =
-- Here you can adjust the parameters of the request
GPT.newCompletionRequest
{ GPT.maxTokens = 512
, GPT.prompt = [trimming|
Question: ${question}
Answer:
|] }
-- | Sets the answer field back to an empty string
clearAnswer :: (?modelContext :: ModelContext) => Question -> IO ()
clearAnswer question = do
sqlExec "UPDATE questions SET answer = '' WHERE id = ?" (Only question.id)
pure ()
-- | Stores a couple of newly received characters to the database
appendToken :: (?modelContext :: ModelContext) => Question -> Text -> IO ()
appendToken question token = do
sqlExec "UPDATE questions SET answer = answer || ? WHERE id = ?" (token, question.id)
pure ()
https://github.com/digitallyinduced/ihp/assets/2072185/79d24883-a6cf-45bf-a2cb-6fde803e2c3b
onlyWhere
, onlyWhereReferences
and onlyWhereReferencesMaybe
:
In IHP code bases you often write filter functions such as these:
getUserPosts user posts =
filter (\p -> p.userId == user.id) posts
This can be written in a shorter way using onlyWhere
:
getUserPosts user posts =
posts |> onlyWhere #userId user.id
Because the userId
field is an Id, we can use onlyWhereReferences
to make it even shorter:
getUserPosts user posts =
posts |> onlyWhereReferences #userId user
If the Id field is nullable, we need to use onlyWhereReferencesMaybe
:
getUserTasks user tasks =
tasks |> onlyWhereReferences #optionalUserId user
GHC 9.2.4 -> 9.4.4 We've moved to a newer GHC version π
Initalizers
You can now run code on the start up of your IHP app using an initializer. For that you can call addInitializer
from your project's Config.hs
.
The following example will print a hello world message on startup:
config = do
addInitializer (putStrLn "Hello World!")
This is especially useful when using IHP's Row level security helpers. If your app is calling ensureAuthenticatedRoleExists
from the FrontController
, you can now move that to the app startup to reduce latency of your application:
config :: ConfigBuilder
config = do
-- ...
addInitializer Role.ensureAuthenticatedRoleExists
Multiple Record Forms
You can now use nestedFormFor
to make nested forms with the IHP form helpers. This helps solve more complex form use cases.
Here's a code example:
renderForm :: Include "tags" Task -> Html
renderForm task = formFor task [hsx|
{textField #description}
<fieldset>
<legend>Tags</legend>
{nestedFormFor #tags renderTagForm}
</fieldset>
<button type="button" class="btn btn-light" data-prototype={prototypeFor #tags (newRecord @Tag)} onclick="this.insertAdjacentHTML('beforebegin', this.dataset.prototype)">Add Tag</button>
{submitButton}
|]
renderTagForm :: (?formContext :: FormContext Tag) => Html
renderTagForm = [hsx|
{(textField #name) { disableLabel = True, placeholder = "Tag name" } }
|]
Faster Initial Startup for large IHP Apps:
The Generated.Types
module is a module generated by IHP based on your project's Schema.sql
. The module is now splitted into multiple sub modules, one for each table in your Schema.sql
. This makes the initial startup of your app faster, as the individual sub modules can now be loaded in parallel by the compiler.
Static Files Changes:
IHP is now using the more actively maintained wai-app-static
instead of wai-middleware-static
for serving files from the static/
directory.
The old wai-middleware-static
had some issues, particular related to leaking file handles. Also wai-app-static
has better cache handling for our dev mode.
You might see some changes related to caching of your app's static files:
static/vendor/
previously had more aggressive caching rules, this is not supported anymore.maxage=0
instead of Cache-Control: nocache
asssetPath
helper, this will not cause any issues.Additionally the routing priority has changed to save some syscall overhead for every request:
Previously:
GET /test.txt
Does file exists static/test.txt?
=> If yes: return file
=> If no: run IHP router to check for an action matching /test.txt
Now:
GET /test.txt
Run IHP router to check for an action matching /test.txt
Is there an action matching this?
=> If yes: Run IHP action
=> If no: Try to serve file static/test.txt?
.env
Files:
Next to the .envrc
, you can now save secrets outside your project repository by putting them into the .env
file.
The .env
is not committed to the repo, so all secrets are safe against being accidentally leaked.
HSX Comments: You can now write Haskell comments inside HSX expressions:
render MyView { .. } = [hsx|
<div>
{- This is a comment and will not render to the output -}
</div>
|]
HSX Doctype: HTML Doctypes are now supported in HSX. Previously you had to write them by using the underlying blaze-html library:
render MyView { .. } = [hsx|
<!DOCTYPE html>
<html lang="en">
<body>
hello
</body>
</html>
|]
accessDeniedWhen
Full Changelog: https://github.com/digitallyinduced/ihp/compare/v1.0.1...v1.1.0
Help decide what's coming next to IHP by using the Feature Voting!
β See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
π§ To stay in the loop, subscribe to the IHP release emails (right at the bottom of the page). Or follow digitally induced on twitter.
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
A new IHP release, containing mostly bug fixes and small improvements to existing features. There's no major new feature in this release π οΈ
Full Changelog: https://github.com/digitallyinduced/ihp/compare/v1.0.0...v1.0.1
Help decide what's coming next to IHP by using the Feature Voting!
β See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
π§ To stay in the loop, subscribe to the IHP release emails (right at the bottom of the page). Or follow digitally induced on twitter.
Two years after the first public release of IHP Iβm very happy and proud to announce the release of version 1.0! π β Read the full release announcement on the IHP Website.
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
π¨ Bootstrap 5:
We've finally upgraded from Bootstrap 4 to Bootstrap 5 π
E.g. all forms rendered with formFor
now expect you're using bootstrap 5 by default. Bootstrap 4 is still supported and available if needed, e.g. if you don't want to update your application.
π» M1 Builds: Thanks to a new mac mini sponsored by MacStadium, we now have prebuilt binaries for Apple M1 devices. Previously it could take up multiple hours to compile everything needed for IHP from scratch. Now this just takes a minute of downloading binaries.
ποΈ Schema Designer: Index Management
The IHP Schema Designer now supports creating, editing and deleting column indexes from the GUI. Previously this was only possible by editing the Schema.sql
manually:
π Smaller & Faster Production Builds We've optimized the nix build process. Previously when building for production, the output of the nix build process contained many dev tools, like the Postgres server and Haskell Language Server. These are not needed in production. With the recent changes they're excluded from the production build. This saves tons of space. E.g. when packaging a simple IHP app, the file size of the docker image moved from 300MB -> 80MB.
The build process now uses all available cores when calling make
. This will speed up builds that rely on many IHP Scripts.
record.field
syntax instead of get #field record
in docs
[HsType]
for TH.Conp
Full Changelog: https://github.com/digitallyinduced/ihp/compare/v0.20.0...v1.0.0
Help decide what's coming next to IHP by using the Feature Voting!
β See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
π§ To stay in the loop, subscribe to the IHP release emails (right at the bottom of the page). Or follow digitally induced on twitter.
This is the second release candidate for the soon-to-be-published IHP v1.0. See https://github.com/digitallyinduced/ihp/issues/1501
See v1.0.0-rc1 for a list of all changes. This release contains small bug fixes only.
This is a release candidate for the soon-to-be-published IHP v1.0. See https://github.com/digitallyinduced/ihp/issues/1501
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
π¨ Bootstrap 5:
We've finally upgraded from Bootstrap 4 to Bootstrap 5 π
E.g. all forms rendered with formFor
now expect you're using bootstrap 5 by default. Bootstrap 4 is still supported and available if needed, e.g. if you don't want to update your application.
π» M1 Builds: Thanks to a new mac mini sponsored by MacStadium, we now have prebuilt binaries for Apple M1 devices. Previously it could take up multiple hours to compile everything needed for IHP from scratch. Now this just takes a minute of downloading binaries.
ποΈ Schema Designer: Index Management
The IHP Schema Designer now supports creating, editing and deleting column indexes from the GUI. Previously this was only possible by editing the Schema.sql
manually:
π Smaller & Faster Production Builds We've optimized the nix build process. Previously when building for production, the output of the nix build process contained many dev tools, like the Postgres server and Haskell Language Server. These are not needed in production. With the recent changes they're excluded from the production build. This saves tons of space. E.g. when packaging a simple IHP app, the file size of the docker image moved from 300MB -> 80MB.
The build process now uses all available cores when calling make
. This will speed up builds that rely on many IHP Scripts.
record.field
syntax instead of get #field record
in docs
[HsType]
for TH.Conp
Full Changelog: https://github.com/digitallyinduced/ihp/compare/v0.20.0...v1.0.0-rc1
Help decide what's coming next to IHP by using the Feature Voting!
β See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
π§ To stay in the loop, subscribe to the IHP release emails (right at the bottom of the page). Or follow digitally induced on twitter.
A new IHP release, containing bug fixes and productivity improvements to existing features π οΈ
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
βͺ Dot Notation:
IHP goes OOP-ish. Thanks to a compiler update in this release we can now make use of dot notation Γ la someThing.someAttribute
π
-- Previously:
get #title project
-- Now:
project.title
This also works in HSX:
[hsx|
<!-- Previously: -->
<div>{get #title project}</div>
<!-- Now: -->
<div>{project.title}</div>
|]
This can especially be useful when you're dealing with nested records, e.g. when working with fetchRelated
:
[hsx|
<!-- Previously: -->
<div>{get #name (get #userId project)}</div>
<!-- Now: -->
<div>{project.userId.name}</div>
|]
π οΈ HSX Improvements:
We've refactored HSX to fix some of the common issues, like HSX not support expressions like allEnumValues @MyEnum
. This refactoring was needed to make dot notation work in HSX as well.
[hsx|
<!-- Previously failed because of the @ symbol: -->
{allEnumValues @Color}
<!-- Just works with IHP 0.20: -->
{allEnumValues @Color}
|]
β New Function: isActiveAction
Returns True
when the given action matches the path of the currently executed action
[hsx|
{isActiveAction PostsAction}
|]
π I18n Routing:
As we're preparing I18n support for IHP apps, we've already refactored the routing to support langauge prefixes, e.g. /de/
for a german site and /en/
for english.
Here's an advanced code example of this in action:
instance FrontController WebApplication where
... -- the previous code assigning controllers is still required
router additionalControllers = do
mlocale <- optional do
string "/"
string "en"
let router = defaultRouter additionalControllers
case mlocale of
Just locale -> case locale of
"en" -> putContextRouter EN_UK router
_ -> IHP.RouterPrelude.error "This should be unreachable"
Nothing -> router
Once IHP has full i18n, you can find more on this in the documentation.
π€ GHC 9.2.4: We've updated to a new major version of GHC. Previously IHP apps were using GHC 8.10.7. This improves support for M1/M2 macs and allows for dot notation.
CREATE POLICY a ON t USING t.f = public.f
contains a qualified sub-expression t.f
. When dumping this policy from pg_dump we get a unqualified f = public.f
condition instead. So we need to normalize the Policy to the normal form CREATE POLICY a ON t USING f = public.f
Full Changelog: https://github.com/digitallyinduced/ihp/compare/v0.19.0...v0.20.0
Help decide what's coming next to IHP by using the Feature Voting!
β See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
π§ To stay in the loop, subscribe to the IHP release emails (right at the bottom of the page). Or follow digitally induced on twitter.
A new IHP release, containing bug fixes and productivity improvements to existing features π οΈ
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
π» Apple M1 Support: This release finally adds support for Apple M1 devices. So we all can get new macbooks now π
π Major DataSync Improvements: We've implemented a lot of improvements to IHP DataSync:
withTransaction
:
import { withTransaction } from 'ihp-datasync';
await withTransaction(async transaction => {
const team = await transaction.createRecord('teams', { title: 'New Team' });
const project = await transaction.createRecord('projects', {
title: 'Project 1',
teamId: team.id
});
return [ team, project ];
})
const todoA = { title: 'Finish Guide', userId: '49946f4d-8a2e-4f18-a399-58e3296ecff5' };
const todoB = { title: 'Learn Haskell', userId: '49946f4d-8a2e-4f18-a399-58e3296ecff5' };
const todos = await createRecord('todos', [ todoA, todoB ]);
π We've also launched Thin Backend, which is IHP DataSync as a Service If you're into Frontend Development, give it a try!
πΆοΈ Dark Mode:
We've redesigned the dev tools and finally added Dark Mode to IHP:
There's also a couple of nice improvements in the UI in general, e.g. you can now add columns such as created_at
or updated_at
with a single click:
In the Data Editor you can now hover over IDs to show the referenced database record in a card:
π¨ Redesigned Migration Workflow: Migrations can now be managed much better from the dev tools. E.g. you can see all migrations, see what has been run already and also manually run migrations here that haven't been executed yet:
π³οΈ Filebase Integration You can now use Filebase as an alternative to S3 for storing files with IHP Storage:
module Config where
import IHP.Prelude
import IHP.Environment
import IHP.FrameworkConfig
import IHP.FileStorage.Config
config :: ConfigBuilder
config = do
option Development
option (AppHostname "localhost")
initFilebaseStorage "my-bucket-name"
π¨ Custom Middlewares:
IHP provides an "escape-hatch" from the framework with the CustomMiddleware
option.
This can be used to run any WAI middleware after IHP's middleware stack, allowing for possibilities
such as embedding a Servant or Yesod app into an IHP app, adding GZIP compression, or any other
number of possibilities. See wai-extra for examples
of WAI middleware that could be added.
The following example sets up a custom middleware that infers the real IP using X-Forwarded-For
and adds a custom header for every request.
module Config where
import Network.Wai.Middleware.AddHeaders (addHeaders)
import Network.Wai.Middleware.RealIp (realIp)
config :: ConfigBuilder
config = do
option $ CustomMiddleware $ addHeaders [("X-My-Header", "Custom WAI Middleware!")] . realIp
useQuerySingleResult
withRLS
to wrap database operations inside a RLS context. This added an overhead of several additional database queries. Now we pack all SET ..
statements right into the query string itself, so it's send to postgres inside a single operation.
where('field', null')
needs to add a SQL condition like WHERE field IS NULL
instead of WHERE field = NULL
[UUID]
in AutoRoute
(
and )
.
ensureIsUser
for the full application
Full Changelog: https://github.com/digitallyinduced/ihp/compare/v0.18.0...v0.19.0
Help decide what's coming next to IHP by using the Feature Voting!
β See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
π§ To stay in the loop, subscribe to the IHP release emails (right at the bottom of the page). Or follow digitally induced on twitter.
A new IHP release, mostly containing bug fixes and productivty improvements to existing features π οΈ
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
πͺStripe Cancelations are Supported now: The IHP Stripe Integration now supports two further events that allow your app to automatically handle with unsubscribes:
on CustomerSubscriptionUpdated { subscription = stripeSubscription } = do
-- ...
on CustomerSubscriptionDeleted { subscriptionId } = do
-- ...
π§° Haskell Language Server: Performance Improvements
The Haskell Language Server provided by IHP is now compiled with the --enable-executable-dynamic
flag. This flag significantly improved performance of using HLS in IHP code bases as reported by multiple people.
If you still have performance issues, please submit issues directly here https://github.com/haskell/haskell-language-server/issues/2340
πͺ Cookies:
While IHP had supported session cookies for a long time now, we never got to add a simple function to add your own non-session cookies. Now it's finally there, meet setCookie
:
import Web.Cookie
action MyAction = do
setCookie defaultSetCookie
{ setCookieName = "exampleCookie"
, setCookieValue = "exampleValue"
}
π Multi-line Strings:
IHP apps now have [trimming|some text|]
in scope by default. This trimming
quasi quoter provides a useful way to write multi line strings, like this:
let myString = [trimming|
My
multi line
string
|]
It's called trimming because the function automatically trims/removes the indentation spaces at the left. So the above string is My\nmulti line\nstring
instead of <4 spaces>My\n<4 spaces>multi line\n<4 spaces>\nstring
.
π« Mail Improvements: You can now easily set reply-to, cc, bcc or custom headers when using the IHP Mailer system:
instance BuildMail ConfirmationMail where
subject = "Subject"
to ConfirmationMail { .. } = Address { addressName = Just "F L", addressEmail = "[email protected]" }
from = "[email protected]"
html ConfirmationMail { .. } = [hsx|
Hello World
|]
-- New:
replyTo ConfirmationMail { .. } =
Just Address { addessName = "Support", addressEmail = "[email protected]" }
cc ConfirmationMail { .. } =
[ Address { addessName = "Some one", addressEmail = "[email protected]" } ]
bcc ConfirmationMail { .. } =
[ Address { addessName = "Some one", addressEmail = "[email protected]" } ]
-- Custom headers
headers ConfirmationMail { .. } =
[ ("X-Mailer", "mail4j 2.17.0")
, ("In-Reply-To", "<[email protected]>")
]
ποΈ deleteRecordByIds
You can now easier delete records when you only know their ids:
let postId :: [Id Post] = ["5280e9a5-3105-45b3-8aea-6a081c596a11", "6761216b-c508-4c88-80fc-66316a1dc88c"]
deleteRecordByIds postId
π fetchLatest
There's now a helper to fetch latest record of something:
latestUser <- query @User |> fetchLatest
-- Previously you had to write query:
latestUser <- query @User
|> orderByDesc #createdAt
|> fetchOneOrNothing
setCookie
function
Full Changelog: https://github.com/digitallyinduced/ihp/compare/v0.17.0...v0.18.0
Help decide what's coming next to IHP by using the Feature Voting!
β See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
π§ To stay in the loop, subscribe to the IHP release emails (right at the bottom of the page). Or follow digitally induced on twitter.
A new IHP release with new features and many bug fixes π
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
π½ Improved Migration Workflow: Since v0.16.0 IHP is already automatically generating a migration prefilled with the recommended changes that need to be applied to your database. Previously IHP was tracking all changes you've made in the Schema Designer and re-applied them to the migration sql file.
With this release the approach has been changed to a database diffing approach: IHP will now diff the Schema.sql against your local database and generate a efficient patch to bring both in sync again.
This also affects the local dev tools. The Update DB
button has been removed entirely. When there are unmigrated changes locally, the Schema Designer will suggest you to generate and run a migration, like this:
π¨βπ» Optional HSX Attributes:
HSX gained special handling for Maybe
values to make it easy to deal with optional attributes.
You can write
let
target :: Maybe Text
target = Just "_blank"
in
[hsx|<a target={target} />|]
and it will render to:
<a target="blank" />
Using Nothing
results in the target
attribute not being in the output HTML:
let
target :: Maybe Text
target = Nothing
in
[hsx|<a target={target} />|]
This will render to:
<a />
:atom: DataSync Updates: IHP DataSync now uses React Functional Components instead of Class based approach used in previous versions::
function TodoList() {
const todos = useQuery(query('todos'));
if (todos === null) {
return <div className="spinner-border text-primary" role="status">
<span className="sr-only">Loading...</span>
</div>;
}
return <div>
{todos.map(todo => <div>{todo.title}</div>)}
</div>
}
Additionally this release contains many bug fixes for DataSync and large performance and usability improvements.
π§° Haskell Language Server: Performance Improvements We've updated the Haskell Language Server from 1.4.0.0 to the latest version 1.5.1.0.
With HLS 1.5.1.0 the performance should be much better. If you're curious, here's a nice presentation of what has changed in HLS.
If you still have performance issues, please submit issues directly here https://github.com/haskell/haskell-language-server/issues/2340
π« Encrypted SMTP
When configuring the IHP Mailer to use SMTP, you can now specify the encryption by setting the encryption
field:
import IHP.Mail
config :: ConfigBuilder
config = do
option $ SMTP
{ host = "smtp.myisp.com"
, port = 2525
, credentials = Nothing
, encryption = STARTTLS -- <-- NEW
}
Full Changelog: https://github.com/digitallyinduced/ihp/compare/v0.16.0...v0.17.0
Help decide what's coming next to IHP by using the Feature Voting!
β See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
π§ To stay in the loop, subscribe to the IHP release emails. Or follow digitally induced on twitter.