PureScript web app for dummies series
Date | Description | Link |
---|---|---|
02 Mar 2022 | set up a basic HTTPure server | https://tiago.dalligna.com/2022/03/purescript-web-app-for-dummies-part-1/ |
07 Mar 2022 | add a client side app | https://tiago.dalligna.com/2022/03/purescript-web-app-for-dummies-part-2/ |
31 Mar 2022 | add a database, API, and unit tests | https://tiago.dalligna.com/2022/03/purescript-web-app-for-dummies-part-3/ |
11 Apr 2022 | connect frontend client to the api | https://tiago.dalligna.com/2022/04/purescript-web-app-for-dummies-part-4/ |
setup
Hello folks, are you interested in purescript? Yes? Then I have something you will definitely enjoy: HTTPure + spork = web application but just in case you dont have it installed in your machine yet
npm install purescript spago --global
Init
you start with a basic package:
- pacakge.json => the npm package usual stuff
- packages.dhall => list of purescript packages (it can contain direct reference to the repository)
- spago.dhall => purescript dependency file (well, spago, but you understand my point right?)
- src/Main.purs => entry point for our webserver
- test/Main.purs => tests file
you can download this files here: in our github repo but you don’t have to.
Here are the steps to get there:
spago init
spago run Main
now you have a purescript project but it doesnt do anything so we modify our main to listen to the port 8080
module Server.Main where
import Prelude
import Effect.Class.Console (log)
import HTTPure (ServerM, serve, ok)
main :: ServerM
main = serve 8080 router $ log "Server now up on port 8080"
where
router _ = ok "hello world!"
now add “httpure” in your list of dependencies:
spago install httpure
spago run Main
browse http://localhost:8080 and you have a web server not a very useful one though…
Adding a Router
Lets start improving this a bit by adding a rounter:
main :: Effect Unit
main = launchAff_ do
let options = {hostname: "localhost", port: 8080, backlog: Nothing}
liftEffect
$ HTTPure.serve' options router
$ log ("Server now up @ " <> options.hostname <> ":" <> show options.port)
router :: HTTPure.Request -> HTTPure.ResponseM
router { body, headers, method, path } = case method, path of
HTTPure.Get, [ ] -> HTTPure.ok $ "hello world"
HTTPure.Get, [ "ping" ] -> HTTPure.ok $ "pong"
_, _ -> do
log $ "Not found: " <> show path
HTTPure.unauthorized
now you will need
spago install node-fs-aff
spago install arrays
spago install maybe
spago install tuples
Rendering static assets
ok, things are starting to get more interesting now but still it is not that great lets render some assets (images/css/js/etc)
This means reading the files
serveFile :: String -> HTTPure.ResponseM
serveFile path = (liftAff $ FS.exists path) >>= case _ of
false -> HTTPure.notFound
true -> (liftAff $ FS.readFile path) >>= (HTTPure.ok)
and sending to the browser
HTTPure.Get, [ "static", filename ] -> serveFile ("src/wwwroot/" <> filename)
now Main is starting to get too big, so lets split router into it’s own file
module Server.Router where
import Prelude
import Data.Array (length, take)
import Data.Tuple.Nested ((/\))
import Effect.Aff.Class (liftAff)
import Effect.Class.Console (log)
import Node.FS.Aff (exists, readFile) as FS
import HTTPure as HTTPure
router :: HTTPure.Request -> HTTPure.ResponseM
router { body, headers, method, path } = case method, path of
HTTPure.Get, [ "ping" ] -> HTTPure.ok $ "pong"
HTTPure.Get, [ ] -> serveFile' "text/html" "src/wwwroot/index.html"
HTTPure.Get, [ "static", filename ] -> serveFile ("src/wwwroot/" <> filename)
HTTPure.Get, _
| startsWith path [ "../" ] -> HTTPure.unauthorized
_, _ -> do
log $ "Not found: " <> show path
HTTPure.unauthorized
startsWith :: Array String -> Array String -> Boolean
startsWith s t = t == (take (length t) s)
serveFile' :: String -> String -> HTTPure.ResponseM
serveFile' contentType path = (liftAff $ FS.exists path) >>= case _ of
false -> HTTPure.notFound
true -> (liftAff $ FS.readFile path) >>= (HTTPure.ok' (HTTPure.headers [ "Content-Type" /\ contentType ]))
serveFile :: String -> HTTPure.ResponseM
serveFile path = (liftAff $ FS.exists path) >>= case _ of
false -> HTTPure.notFound
true -> (liftAff $ FS.readFile path) >>= (HTTPure.ok)
The code is available in the tag v1 of our pnotes repo.
Wrap up
In this post I tried to help you go through the very basics of how to make a purescript web server It is the first post of a series that will take this into a functional note taking app, with sqlite database and spork as the client side
I hope you have enjoyed and till next one