Realworld app (elm-spa-example) in elm-pages v3

Hello everyone!

As I’m nearing the elm-pages v3 release, I’m building a few examples to showcase some of the new features. Since elm-spa-example is the canonical Elm app, I went ahead and created a version of that with elm-pages.

I started with the code from GitHub - ryannhg/elm-spa-realworld: The RealWorld example app built with elm-spa! since elm-pages and elm-spa both have file-based routing, so that was a more similar starting point to fork from.

Here’s the live demo (runs on Netlify with server-rendered HTML running on serverless functions - you can even disable JavaScript in your browser and try it out, including login, favoring, article creation, etc.).

https://elm-pages-realworld.netlify.app/

And the code is here:

I wanted to keep the functionality as similar as possible to the elm-spa-example and realworld spec, so I refrained from adding some of the things that elm-pages is best at, like pending/optimistic UI, adding meta tags using resolved data for a given route, doing dynamic client-side validations, and directly making database queries in the data function. This is actually kind of cool in a way because it leaves those features as an exercise with the baseline being more simple. Might be the topic for a workshop exercise in the future or a video demo!

The only places where I deviated from the elm-spa-example behavior where places where writing idiomatic elm-pages server-rendered code would require me to make a change. For example, I used Forms whenever possible, including GET forms, which navigate to a URL with a query parameter when you submit them (this is how I handle pagination and tag filters). So the elm-spa-example doesn’t use URL query params to manage this, but I made this slight change here. I prefer this because it makes these URLs sharable, but it also lets you leverage elm-pages form features to reduce the amount of work to wire things up.

A few things to notice:

  • None of the routes use Model, init, or update whatsoever!
  • There are no loading spinners - loading states are handled on the server (the data BackendTask’s are resolved before the initial page HTML is sent over). This often works out nicely because your server can make faster round-trips to fetch data since it’s in the same data center (as opposed to making round trips between the user’s Browser and your data center, which may be far apart). In the case of the realworld API that I’m hitting here, it’s pretty slow so this might not be ideal, but that’s the general idea. I might make a full-stack version of this demo that implements the backend as well - it turns out, this won’t be a lot more work than reaching out to an external API, and it will give us more control over the performance as well.

There’s a lot more to share about the upcoming elm-pages v3 release, so stay tuned! Feel free to share questions or feedback here, or join the #elm-pages channel in the Elm Slack. You can try out the v3 beta with this starter repo as well. Cheers!

16 Likes

Hi @dillonkearns, thank you, this is amazing stuff!

I noted that moving back and forward is slower when JavaScript is activated compared to navigating without JavaScript.

I wonder if this is because content.dat is not cached by the browser

I got pretty deep into nextjs for static sites in 2021. At that time, I started to read about how it could be used in a context like blitz js (and remix?), where you use it as a front end for a server rendered dynamic/CRUD application. In that context, it sounded useful because the server side data fetching could simply use javascript functions to fetch data from a data store.

In the context of elm pages v3, if you attempted to imitate blitz js, it would be impossible to use elm to actually fetch from a database directly ( or at least impossible without lamdera?). In that case, did I understand your explanation correctly - you are suggesting using server side API calls written in elm to fetch from a backend API?

I believe the speed is roughly the same, but the perceived slowness you are seeing is that the elm-spa version shows “Loading…” when it’s navigating back. The API responses aren’t cached in the elm-spa version either so there’s no difference with regard to caching API responses.

I went ahead and used the transition state that elm-pages provides to show “Loading…”: Show loading when transition is present. · dillonkearns/elm-pages-realworld@b37642d · GitHub.

To add a little more polish to it, I added some CSS here to wait 500ms before showing the loading text: Only show loading text for page loads over 500ms. · dillonkearns/elm-pages-realworld@6aba5a3 · GitHub. That way the happy path doesn’t have a flash of loading indicator.

Really the idea with an elm-pages app is that you want to host your data and your elm-pages app in the same datacenter, and have quick data loads. In cases where there really is slow processing, you wouldn’t use the elm-pages Route Modules’ data to load up that data but would instead use the more traditional TEA approach of making an API request from init with elm/http. elm-pages allows you to use either approach, but you can simplify your code and take advantage of the features that elm-pages provides in cases where you can use that kind of architecture and resolve data in a performant way. So this application that hits a fairly slow API isn’t the best illustration of these features, but I think all the more reason that doing this with the backend implemented in elm-pages would be a really neat demo as well!

1 Like

It’s actually very possible to fetch from the database directly with elm-pages v3! We’re running an Elm app in a Node context. You can imagine doing this through a port. elm-pages actually exposes something that uses a port in its generated code under the hood, but it gives you a nice abstraction for running it, BackendTask.Custom.run.

You can see the docs for this here: BackendTask.Custom - elm-pages-v3-beta 21.0.0. You give it a JSON Encoder and Decoder and it resolves that data from your custom-backend-task.ts file.

This elm-pages v3 TodoMVC app does exactly that using Prisma, for example: https://github.com/dillonkearns/elm-pages-v3-beta/blob/master/examples/todos/app/Route/Visibility__.elm and https://github.com/dillonkearns/elm-pages-v3-beta/blob/master/examples/todos/custom-backend-task.ts. You can try a hosted version of this app at https://elm-pages-todos.netlify.app/.

2 Likes

In nextjs, you add state hooks and add event handlers. Is it possible to make an elm page interactive with an update function?

Yes, you have your standard Elm init, update, Model, and subscriptions, in addition to the things that elm-pages gives you, so you can do whatever you might be used to doing in a regular Elm application.

The framework also manages a lot of state for you, so the best practices with elm-pages v3 is to rely on this state instead of using your Model/update when possible. I think of it as building things declaratively rather than imperatively.

In the TodoMVC example I linked to above, there is optimistic UI which is all done through the form submission state that the framework manages for you (so the framework’s internal Model which it exposes through some high-level APIs). If you try out the UI you can see that it shows optimistic updates, it’s easier to see if you slow down requests in the network tab.

I could get really excited about a starter with postgres + flask rest + pages v3, that ran on render or something similar. That is essentially what I would like to train in, in the long run. I have to reflect on whether spa or server rendered will become more popular overall before I choose between elm-spa or similar and pages-v3.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.