A purely functional full-stack web app
by Flavio Corpa
- •
- December 14, 2020
- •
- backend• elm-graphql• elm• frontend• fullstack• functional programming• functional• graphql• haskell• mu-graphql• mu-haskell• mu
- |
- 12 minutes to read.

Imagine that you had a way to keep your backend code in sync with your frontend code, both generating code (at the type-level even!) and getting compile time guarantees that both sides are using the same version of your schemas. Wouldn’t that be awesome? 😎
This is exactly what an example full-stack web app we’ve created at 47 Degrees accomplishes! 🙌🏼 This is the dream stack we’ve used: Haskell with mu-haskell
and mu-graphql
for the server, and Elm
with elm-graphql
for the client. You can follow along or checkout the full code here!
Here is the running library app in the browser!
Our source of truth: GraphQL Schemas
We love every kind of book 📚. That’s why we decided some time ago to create a GraphQL library example to feature all the awesomeness we wanted to talk about in this blogpost.
For the communication, we are going to use GraphQL rather than the most commonly used REST. GraphQL has many advantages; in our case, the main one is that, by writing a GraphQL schema, we can use it as the source of truth for our backend and frontend code. Here are the types for our library.graphql
definition:
type Book {
id: Int!
title: String!
imageUrl: String!
author: Author!
}
type Author {
id: Int!
name: String!
books: [Book!]!
}
input NewAuthor {
name: String!
}
input NewBook {
title: String!
authorId: Int!
imageUrl: String!
}
Did you notice all those exclamation marks? !
means “mandatory” in GraphQL; otherwise, fields are considered optional. This means, for example, that our server code will generate Maybe
types in Haskell for them, but we don’t want to complicate the example for now.
The input
types are types that describe data that is going to be sent from the client in this case to add new authors and books to the database.
type Query {
authors(name: String! = "%"): [Author!]!
books(title: String! = "%"): [Book!]!
}
type Mutation {
newAuthor(author: NewAuthor!): Author!
newBook(book: NewBook!): Book!
}
type Subscription {
allBooks: Book!
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
As you can see, even in a fun example project, we are going to use a lot of GraphQL features, including: Queries, Mutations, and . . . 🥁 Subscriptions!
If you are familiar with HTTP, Query
is going to be your typical GET
endpoint, but for ALL the possible types in our app (as GraphQL generally exposes a single endpoint for everything 💖). Mutation
corresponds with PUT
, POST
, and DELETE
. And Subscription
is typically used for real-time communication, in our case, implemented via WebSockets.
Server Side → Haskell
Because this is just an example project, does that mean we won’t be using a database or any kind of tracing whatsoever? Wrong! 🙅🏼♂️ We are going to use the amazing persistent
to provide us with the database and for tracing one of the industry-standard metrics libraries, Promethus (via the brand new mu-prometheus
💪🏼).
module Schema where
-- imports skipped...
graphql "Library" "library.graphql" -- all the magic here! 🪄🎩
-- more code skipped...
Basically, all the magic happens with that line. Here, we are generating type-level representations of our GraphQL Schemas to help build our type-safe backend code!
-- ... following the above code...
share
[mkPersist sqlSettings, mkMigrate "migrateAll"]
[persistLowerCase|
Author json
name T.Text
UniqueName name
deriving Show Generic
Book json
title T.Text
imageUrl T.Text
author AuthorId
UniqueTitlePerAuthor title author
deriving Show Generic
|]
After that, we have pretty generic persistent
code that describes how these entities are mapped to the database and to Haskell types. In some sense, persistent
is an ORM for Haskell. The weird [| |]
syntax comes from TemplateHaskell
, the “macro” or “meta programming” facility in Haskell, which is able to generate those types automagically. 🪄
As mentioned, you can check the full code in our example repo, but here is the main gist of how you would implement such a GraphQL server by using mu-haskell
and mu-graphql
:
-- language extensions and imports omitted!
main :: IO ()
main = do
p <- initPrometheus "library"
runStderrLoggingT $
withSqliteConn ":memory:" $ \conn -> do
logInfoN "starting GraphQL server on port 8080"
liftIO $
run 8080 $
graphQLApp
(prometheus p $ libraryServer conn)
(Proxy @('Just "Query"))
(Proxy @('Just "Mutation"))
(Proxy @('Just "Subscription"))
-- ...more code skipped...
The line p <- initPrometheus "library"
is basically all there needs to be to set up tracing with mu-prometheus
! We are also able to show logging in our entire app thanks to monad-logger
and its runStderrLoggingT
function, we start the connection with the database with withSqliteConn
, and we start running the server on the port 8080
.
-- following the above code...
libraryServer :: SqlBackend -> ServerT ObjectMapping i Library ServerErrorIO _
libraryServer conn =
resolver
( object @"Book"
( field @"id" bookId,
field @"title" bookTitle,
field @"author" bookAuthor,
field @"imageUrl" bookImage
),
object @"Author"
( field @"id" authorId,
field @"name" authorName,
field @"books" authorBooks
),
object @"Query"
( method @"authors" allAuthors,
method @"books" allBooks
),
object @"Mutation"
( method @"newAuthor" newAuthor,
method @"newBook" newBook
),
object @"Subscription"
(method @"allBooks" allBooksConduit)
)
where
bookId :: Entity Book -> ServerErrorIO Integer
bookId (Entity (BookKey k) _) = pure $ toInteger k
bookTitle :: Entity Book -> ServerErrorIO T.Text
bookTitle = ... -- a lot more resolvers
This is the actual server implementation; each function that we are defining in our where
block is a GraphQL resolver, which each extracts a particular piece of data, and then mu-graphql
joins everything together, so to speak.
Neat, right? 🤩
Client Side → Elm
Elm is a purely functional language for the frontend, heavily inspired by Haskell, with one of the friendliest compiler error messages in the world 🌎. Seriously! Of course we’d love it at 47 Degrees! 😍
-- ...more code...
view : Model -> Html Msg
view model =
div []
[ button [ onClick OpenEditorClicked ] [ text "Add a book" ]
, button [ onClick SubscribeToBooks, disabled model.isSubscribed ]
[ text "Try subscriptions" ]
, input
[ type_ "text"
, placeholder "For example, Kant"
, value model.query
, onInput QueryChanged
]
[]
, showSearchResponse model.searchResponse
]
If you have used Elm before, you already know that the Model
is a type that represents the state of all your app, and the type signature for view
conveys the idea that it returns some kind of HTML that can produce messages (or dispatch actions, like in Redux) of type Msg
.
What about button [ onClick OpenEditorClicked ] [ text "Add a book" ]
? Well, if you haven’t written Elm before, maybe the idea of writing DOM nodes using functions (or combinators) is new to you. But it shouldn’t be difficult to grasp; the first parameter it receives is a list of HTML attributes and the second a list of children DOM nodes. Think of this as writing JSX in React, but with extra compiler validation! 💪🏼
Our UI code is going to be quite simple, but we’ll explain where the magic is for the frontend in the next section.
A match made in heaven: mu-graphql
+ elm-graphql
👼🏼
Let’s look at a more significant piece of code from the frontend:
-- some more imports...
import Graphql.SelectionSet as SelectionSet exposing (SelectionSet)
import LibraryApi.Object exposing (Author, Book)
import LibraryApi.Object.Author as Author
import LibraryApi.Object.Book as Book
import LibraryApi.Subscription as Subscription
-- ...more irrelevant code omitted...
authorSelection : SelectionSet AuthorData Author
authorSelection =
SelectionSet.map AuthorData Author.name
bookSelection : SelectionSet BookData Book
bookSelection =
SelectionSet.map3 BookData
Book.title
Book.imageUrl
(Book.author Author.name)
Did you notice those interesting Book
and Author
types? Where are they coming from? Well, you’ll be pleasantly surprised to hear that they are also AUTO-GENERATED from our library.graphql
schema using Dillon Kearns’s awesome library elm-graphql
!!! 🤯🤯🤯
Yes, the whole LibraryApi
module is COMPLETELY auto-generated each time from your schema just by running npm run codegen
or yarn codegen
! 🥰 Of course, you still need to define the queries and the things you want to get from the server. But at least you didn’t have to write the Schema
types on your own! 👏🏼
This means that, thanks to the combination of the two libraries (mu-graphql
+ elm-graphql
), our frontend and backend code can’t ever be out of sync! Each side’s compiler will read the GraphQL Schema file and give you compile time errors if something has changed and has not been correctly handled on each side! 🦄🌈
Acknowledgments
We had created this app as an example some time ago, and created Github Issues tagged with the hacktoberfest
hashtag to add further improvements whenever we could find some time. That’s why we really have to thank our friend @logachev_dev
for his awesome contributions, both to the original Elm code (as well as tons and tons of CSS 💅🏼) and the Haskell code as well! 🙌🏼
Conclusion
We thought this web stack selection was too AWESOME 😎 to not write about it. We hope that you love it too, and you should totally try it out for your future web apps as well!
If you have questions regarding the code or any other doubts, feel free to write to us on Twitter and also by opening Issues in the original repo! 😉
Happy Hacking! 🦄
The active development of Mu-Haskell (and mu-graphql
) is proudly sponsored by 47 Degrees, a Functional Programming consultancy with a focus on the Scala, Kotlin, Swift, and Haskell programming languages.