Skip to content

Releases: digitallyinduced/ihp

v1.3.0

05 May 00:45
Compare
Choose a tag to compare

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 many small quality of life improvements to IHP, fixes lots of bugs and comes with several helpful documentation improvements. There's been over 190 commits since v1.2.0

Major Changes

  • New CLI command: new-session-secret:
    Until now it's always been a bit tricky to get a new secret for the IHP_SESSION_SECRET env var. To fix that we've added a new new-section-secret command. Run it from the command line inside your IHP project, and it will generate a new random key to use for the IHP_SESSION_SECRET env var:

    $ new-session-secret
    1J8jtRW331a0IbHBCHmsFNoesQUNFnuHqY8cB5927KsoV5sYmiq3DMmvsYk5S7EDma9YhqZLZWeTFu2pGOxMT2F/5PnifW/5ffwJjZvZcJh9MKPh3Ez9fmPEyxZBDxVp
  • Proper support for non id primary keys:
    Previously IHP assumed that your tables primary keys are called id, and that you don't use composite primary keys.

    With this release the following SQL schema will compile without any errors:

    CREATE TABLE bits (
        bit_arbitrary_ident UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL
    );
    CREATE TABLE parts (
        part_arbitrary_ident UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL
    );
    CREATE TABLE bit_part_refs (
        bit_ref UUID NOT NULL,
        part_ref UUID NOT NULL,
        PRIMARY KEY(bit_ref, part_ref)
    );
  • GHC Upgrade: 9.4 -> 9.8.2

  • New Function: sqlQuerySingleRow:
    Runs a raw sql query, that is expected to return a single result row
    Like 'sqlQuery', but useful when you expect only a single row as the result

    do
        user <- sqlQuerySingleRow "SELECT id, firstname, lastname FROM users WHERE id = ?" (Only userId)
  • New Function: sqlExecDiscardResult:
    Runs a sql statement (like a CREATE statement), but doesn't return any result

    sqlExecDiscardResult "CREATE TABLE users ()" ()

    There's now also updateRecordDiscardResult, which is like updateRecord, but doesn't return the updated record.

  • Worker support exponential backoff:

    instance Job DraftEmailJob where
        backoffStrategy = ExponentialBackoff { delayInSeconds = 30 }
        perform job = do
            -- ...

Minor Changes

Docs

We've made some major improvements to the Deployment Guide. With a focus on deploying to NixOS servers running on AWS EC2 and how to connect your server to CloudWatch.

Full Changelog: v1.2.0...v1.3.0

Updating

→ See the UPGRADE.md for upgrade instructions.


If you have any problems with updating, [let us know on the IHP Discourse](https://discuss.ihp....

Read more

v1.2.0

15 Nov 01:56
Compare
Choose a tag to compare

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.

Major Changes

  • 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

    This command will build your app, create necessary systemd services for the app and any IHP workers, install postgres and fill it up with your app's Schema.sql and Fixtures.sql and registers a migrate command on the server that runs the latest database migrations. It also puts a nginx with letsencrypt in front to handle HTTPS requests out of the box.

    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.

    Check out the docs for more information.

  • 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}/>

Minor Changes

Read more

v1.1.0

21 Jul 14:32
e6c6eaf
Compare
Choose a tag to compare

IHP v1.1.0 is out now

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.

Major Changes

  • 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 ()
    Bildschirmaufnahme.2023-04-17.um.23.48.41.mov
  • 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" } }
    |]

    You can find a demo app here.

  • 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:

    • files in static/vendor/ previously had more aggressive caching rules, this is not supported anymore.
    • files in dev mode are now cached with maxage=0 instead of Cache-Control: nocache
    • application assets are now cached forever. As long as you're using IHP's 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>
    |]

Minor Changes

Read more

v1.0.1

06 Feb 09:07
Compare
Choose a tag to compare

IHP v1.0.1 is out now

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 🛠️

Minor Changes

Notable Documentation Changes

Full Changelog: v1.0.0...v1.0.1

Feature Voting

Help decide what's coming next to IHP by using the Feature Voting!

Updating

→ 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.

v1.0.0

24 Oct 14:22
Compare
Choose a tag to compare

IHP v1.0.0 is out now

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.

Major Changes

  • 🎨 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:

    image
  • 🐎 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.

Other Changes

Full Changelog: v0.20.0...v1.0.0

Feature Voting

Help decide what's coming next to IHP by using the Feature Voting!

Updating

→ See the UPGRADE.md for upgrade instructions.


If you have any problems with updating, [let us know on the IH...

Read more

v1.0-RC2

21 Oct 14:31
d45ff89
Compare
Choose a tag to compare
v1.0-RC2 Pre-release
Pre-release

This is the second release candidate for the soon-to-be-published IHP v1.0. See #1501

See v1.0.0-rc1 for a list of all changes. This release contains small bug fixes only.

Other Changes

v1.0-RC1

18 Oct 09:52
05f1eaf
Compare
Choose a tag to compare

IHP v1.0.0-rc1 is out now

This is a release candidate for the soon-to-be-published IHP v1.0. See #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.

Major Changes

  • 🎨 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:

    image
  • 🐎 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.

Other Changes

Full Changelog: v0.20.0...v1.0.0-rc1

Feature Voting

Help decide what's coming next to IHP by using the Feature Voting!

Updating

→ 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.

v0.20.0

30 Aug 08:51
Compare
Choose a tag to compare

IHP v0.20.0 is out now

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.

Major Changes

  • ⚪ 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.

Other Changes

Full Changelog: v0.19.0...v0.20.0

Feature Voting

Help decide what's coming next to IHP by using the Feature Voting!

Updating

→ 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.

v0.19.0

04 May 17:06
Compare
Choose a tag to compare

IHP v0.19.0 is out now

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.

Major Changes

  • 💻 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:

    • Limits and Offsets
    • Transactons using 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 ];
      })
    • Batch Inserts, Batch Deletes, Batch Creates
      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 ]);
    • Distinct on
    • Optimistic Updates
    • Full Text Search
    • Performance Improvements

    🚀 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:
    image
    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:

    image

    In the Data Editor you can now hover over IDs to show the referenced database record in a card:

    image
  • 🎨 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:
    image

  • 🗳️ 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"

    Learn more in the Docs.

  • 🔨 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

Other Changes

Read more

v0.18.0 (Beta 25.01.2022)

25 Jan 06:41
Compare
Choose a tag to compare

IHP v0.18.0 is out now

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.

Major Changes

  • 🚪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
            -- ...

    Learn how to handle unsubscribes in the docs.

  • 🧰 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 haskell/haskell-language-server#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

Other Changes

Read more