Posts tagged "REST" on abhinavsarkar.nethttps://abhinavsarkar.net/tags/REST/feed.atom2017-10-01T00:00:00ZAbhinav Sarkarhttps://abhinavsarkar.net/about/abhinav@abhinavsarkar.nethttps://abhinavsarkar.net/images/favicon.ico© 2017–2023, Abhinav Sarkarhttps://abhinavsarkar.net/posts/ps-simple-rest-service-2/Writing a Simple REST Web Service in PureScript—Part 22017-10-01T00:00:00ZAbhinav Sarkarhttps://abhinavsarkar.net/about/abhinav@abhinavsarkar.net<p>To recap, in the <a href="https://abhinavsarkar.net/posts/ps-simple-rest-service/?mtm_campaign=feed">first</a> part of this two-part tutorial, we built a simple JSON <a href="https://en.wikipedia.org/wiki/REST" target="_blank" rel="noopener">REST</a> web service in <a href="http://purescript.org" target="_blank" rel="noopener">PureScript</a> to create, update, get, list and delete users, backed by a Postgres database. In this part we’ll work on the rest of the features. <p>This post was originally published on <a href="https://abhinavsarkar.net/posts/ps-simple-rest-service-2/?mtm_campaign=feed">abhinavsarkar.net</a>.</p><!--more--> The requirements are:</p>
<ol type="1">
<li>validation of API requests.</li>
<li>reading the server and database configs from environment variables.</li>
<li>logging HTTP requests and debugging info.</li>
</ol>
<nav id="toc" class="right-toc"><h3>Contents</h3><ol><li><a href="#bugs">Bugs!</a></li><li><a href="#validation">Validation</a></li><li><a href="#configuration">Configuration</a></li><li><a href="#logging">Logging</a></li><li><a href="#conclusion">Conclusion</a></li></ol></nav>
<p>But first,</p>
<h2 data-track-content data-content-name="bugs" data-content-piece="ps-simple-rest-service-2" id="bugs">Bugs!</h2>
<p>What happens if we hit a URL on our server which does not exist? Let’s fire up the server and test it:</p>
<pre class="plain"><code>$ pulp --watch run</code></pre>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/random
HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Length: 148
Content-Security-Policy: default-src 'self'
Content-Type: text/html; charset=utf-8
Date: Sat, 30 Sep 2017 08:23:20 GMT
X-Content-Type-Options: nosniff
X-Powered-By: Express
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /v1/random</pre>
</body>
</html></code></pre>
<p>We get back a default HTML response with a 404 status from <a href="https://expressjs.com" target="_blank" rel="noopener">Express</a>. Since we are writing a JSON API, we should return a JSON response in this case too. We add the following code in the <code>src/SimpleService/Server.purs</code> file to add a catch-all route and send a 404 status with a JSON error message:</p>
<div class="sourceCode" id="cb3" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Either</span> (fromRight)</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.String.Regex</span> (<span class="dt">Regex</span>, regex) <span class="kw">as</span> <span class="dt">Re</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.String.Regex.Flags</span> (noFlags) <span class="kw">as</span> <span class="dt">Re</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.App</span> (<span class="dt">App</span>, all, delete, get, http, listenHttp, post, useExternal)</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Response</span> (sendJson, setStatus)</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Partial.Unsafe</span> (unsafePartial)</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="ot">allRoutePattern ::</span> <span class="dt">Re.Regex</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a>allRoutePattern <span class="ot">=</span> unsafePartial <span class="op">$</span> fromRight <span class="op">$</span> Re.regex <span class="st">"/.*"</span> Re.noFlags</span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="ot">app ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">App</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a>app pool <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a> useExternal jsonBodyParser</span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a> get <span class="st">"/v1/user/:id"</span> <span class="op">$</span> getUser pool</span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a> delete <span class="st">"/v1/user/:id"</span> <span class="op">$</span> deleteUser pool</span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a> post <span class="st">"/v1/users"</span> <span class="op">$</span> createUser pool</span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a> patch <span class="st">"/v1/user/:id"</span> <span class="op">$</span> updateUser pool</span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a> get <span class="st">"/v1/users"</span> <span class="op">$</span> listUsers pool</span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a> <span class="fu">all</span> allRoutePattern <span class="kw">do</span></span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a> setStatus <span class="dv">404</span></span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a> sendJson {<span class="fu">error</span><span class="op">:</span> <span class="st">"Route not found"</span>}</span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a> patch <span class="ot">=</span> http (<span class="dt">CustomMethod</span> <span class="st">"patch"</span>)</span></code></pre></div>
<p><code>allRoutePattern</code> matches all routes because it uses a <code>"/.*"</code> <a href="https://en.wikipedia.org/wiki/Regular_expression" target="_blank" rel="noopener">regular expression</a>. We place it as the last route to match all the otherwise unrouted requests. Let’s see what is the result:</p>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/random
HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Length: 27
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Sep 2017 08:46:46 GMT
ETag: W/"1b-772e0u4nrE48ogbR0KmKfSvrHUE"
X-Powered-By: Express
{
"error": "Route not found"
}</code></pre>
<p>Now we get a nicely formatted JSON response.</p>
<p>Another scenario is when our application throws some uncaught error. To simulate this, we shut down our postgres database and hit the server for listing users:</p>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/users
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Length: 372
Content-Security-Policy: default-src 'self'
Content-Type: text/html; charset=utf-8
Date: Sat, 30 Sep 2017 08:53:40 GMT
X-Content-Type-Options: nosniff
X-Powered-By: Express
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Error: connect ECONNREFUSED 127.0.0.1:5432<br> &nbsp; &nbsp;at Object._errnoException (util.js:1026:11)<br> &nbsp; &nbsp;at _exceptionWithHostPort (util.js:1049:20)<br> &nbsp; &nbsp;at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1174:14)</pre>
</body>
</html></code></pre>
<p>We get another default HTML response from Express with a 500 status. Again, in this case we’d like to return a JSON response. We add the following code to the <code>src/SimpleService/Server.purs</code> file:</p>
<div class="sourceCode" id="cb6" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff.Exception</span> (message)</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.App</span> (<span class="dt">App</span>, all, delete, get, http, listenHttp, post, useExternal, useOnError)</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="ot">app ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">App</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a>app pool <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a> <span class="co">-- previous code</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a> useOnError \err <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a> setStatus <span class="dv">500</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a> sendJson {<span class="fu">error</span><span class="op">:</span> message err}</span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a> patch <span class="ot">=</span> http (<span class="dt">CustomMethod</span> <span class="st">"patch"</span>)</span></code></pre></div>
<p>We add the <code>useOnError</code> handler which comes with <a href="https://pursuit.purescript.org/packages/purescript-express" target="_blank" rel="noopener"><code>purescript-express</code></a> to return the error message as a JSON response. Back on the command-line:</p>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/users
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Length: 47
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Sep 2017 09:01:37 GMT
ETag: W/"2f-cJuIW6961YCpo9TWDSZ9VWHLGHE"
X-Powered-By: Express
{
"error": "connect ECONNREFUSED 127.0.0.1:5432"
}</code></pre>
<p>It works! Bugs are fixed now. We proceed to add next features.</p>
<h2 data-track-content data-content-name="validation" data-content-piece="ps-simple-rest-service-2" id="validation">Validation</h2>
<p>Let’s recall the code to update a user from the <code>src/SimpleService/Handler.purs</code> file:</p>
<div class="sourceCode" id="cb8" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="ot">updateUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>updateUser pool <span class="ot">=</span> getRouteParam <span class="st">"id"</span> <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User ID is required"</span> }</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> sUserId <span class="ot">-></span> <span class="kw">case</span> fromString sUserId <span class="kw">of</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User ID must be positive: "</span> <span class="op"><></span> sUserId }</span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> userId <span class="ot">-></span> getBody <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a> <span class="dt">Left</span> errs <span class="ot">-></span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> intercalate <span class="st">", "</span> <span class="op">$</span> <span class="fu">map</span> renderForeignError errs}</span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a> <span class="dt">Right</span> (<span class="dt">UserPatch</span> userPatch) <span class="ot">-></span> <span class="kw">case</span> unNullOrUndefined userPatch<span class="op">.</span>name <span class="kw">of</span></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respondNoContent <span class="dv">204</span></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> userName <span class="ot">-></span> <span class="kw">if</span> userName <span class="op">==</span> <span class="st">""</span></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">then</span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User name must not be empty"</span> }</span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">else</span> <span class="kw">do</span></span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a> savedUser <span class="ot"><-</span> liftAff <span class="op">$</span> PG.withConnection pool \conn <span class="ot">-></span> PG.withTransaction conn <span class="kw">do</span></span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a> P.findUser conn userId <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">pure</span> <span class="dt">Nothing</span></span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> (<span class="dt">User</span> user) <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> user' <span class="ot">=</span> <span class="dt">User</span> (user { name <span class="ot">=</span> userName })</span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a> P.updateUser conn user'</span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> <span class="op">$</span> <span class="dt">Just</span> user'</span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> savedUser <span class="kw">of</span></span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">404</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User not found with id: "</span> <span class="op"><></span> sUserId }</span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> user <span class="ot">-></span> respond <span class="dv">200</span> (encode user)</span></code></pre></div>
<p>As we can see, the actual request handling logic is obfuscated by the request validation logic for the user id and the user name patch parameters. We also notice that we are using three constructs for validation here: <code>Maybe</code>, <code>Either</code> and <code>if-then-else</code>. However, we can use just <code>Either</code> to subsume all these cases as it can “carry” a failure as well as a success case. <code>Either</code> also comes with a nice monad transformer <a href="https://pursuit.purescript.org/packages/purescript-transformers/3.4.0/docs/Control.Monad.Except.Trans#t:ExceptT" target="_blank" rel="noopener"><code>ExceptT</code></a> which provides the <code>do</code> syntax for failure propagation. So we choose <code>ExceptT</code> as the base construct for our validation framework and write functions to upgrade <code>Maybe</code> and <code>if-then-else</code> to it. We add the following code to the <code>src/SimpleService/Validation.purs</code> file:</p>
<div class="sourceCode" id="cb9" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">SimpleService.Validation</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a> (<span class="kw">module</span> <span class="dt">MoreExports</span>, <span class="kw">module</span> <span class="dt">SimpleService.Validation</span>) <span class="kw">where</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Except</span> (<span class="dt">ExceptT</span>, except, runExceptT)</span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Either</span> (<span class="dt">Either</span>(..))</span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Maybe</span> (<span class="dt">Maybe</span>(..))</span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Handler</span> (<span class="dt">HandlerM</span>, <span class="dt">Handler</span>)</span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Response</span> (sendJson, setStatus)</span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Types</span> (<span class="dt">EXPRESS</span>)</span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Except</span> (except) <span class="kw">as</span> <span class="dt">MoreExports</span></span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">Validation</span> eff a <span class="ot">=</span> <span class="dt">ExceptT</span> <span class="dt">String</span> (<span class="dt">HandlerM</span> (<span class="ot">express ::</span> <span class="dt">EXPRESS</span> <span class="op">|</span> eff)) a</span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a><span class="ot">exceptMaybe ::</span> <span class="kw">forall</span> e m a<span class="op">.</span> <span class="dt">Applicative</span> m <span class="ot">=></span> e <span class="ot">-></span> <span class="dt">Maybe</span> a <span class="ot">-></span> <span class="dt">ExceptT</span> e m a</span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a>exceptMaybe e a <span class="ot">=</span> except <span class="op">$</span> <span class="kw">case</span> a <span class="kw">of</span></span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> x <span class="ot">-></span> <span class="dt">Right</span> x</span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="dt">Left</span> e</span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a><span class="ot">exceptCond ::</span> <span class="kw">forall</span> e m a<span class="op">.</span> <span class="dt">Applicative</span> m <span class="ot">=></span> e <span class="ot">-></span> (a <span class="ot">-></span> <span class="dt">Boolean</span>) <span class="ot">-></span> a <span class="ot">-></span> <span class="dt">ExceptT</span> e m a</span>
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a>exceptCond e cond a <span class="ot">=</span> except <span class="op">$</span> <span class="kw">if</span> cond a <span class="kw">then</span> <span class="dt">Right</span> a <span class="kw">else</span> <span class="dt">Left</span> e</span>
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a><span class="ot">withValidation ::</span> <span class="kw">forall</span> eff a<span class="op">.</span> <span class="dt">Validation</span> eff a <span class="ot">-></span> (a <span class="ot">-></span> <span class="dt">Handler</span> eff) <span class="ot">-></span> <span class="dt">Handler</span> eff</span>
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a>withValidation action handler <span class="ot">=</span> runExceptT action <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb9-26"><a href="#cb9-26" aria-hidden="true" tabindex="-1"></a> <span class="dt">Left</span> err <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb9-27"><a href="#cb9-27" aria-hidden="true" tabindex="-1"></a> setStatus <span class="dv">422</span></span>
<span id="cb9-28"><a href="#cb9-28" aria-hidden="true" tabindex="-1"></a> sendJson {<span class="fu">error</span><span class="op">:</span> err}</span>
<span id="cb9-29"><a href="#cb9-29" aria-hidden="true" tabindex="-1"></a> <span class="dt">Right</span> x <span class="ot">-></span> handler x</span></code></pre></div>
<p>We re-export <code>except</code> from the <code>Control.Monad.Except</code> module. We also add a <code>withValidation</code> function which runs an <code>ExceptT</code> based validation and either returns an error response with a 422 status in case of a failed validation or runs the given action with the valid value in case of a successful validation.</p>
<p>Using these functions, we now write <code>updateUser</code> in the <code>src/SimpleService/Handler.purs</code> file as:</p>
<div class="sourceCode" id="cb10" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Trans.Class</span> (lift)</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Bifunctor</span> (lmap)</span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foreign</span> (<span class="dt">ForeignError</span>, renderForeignError)</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.List.NonEmpty</span> (toList)</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.List.Types</span> (<span class="dt">NonEmptyList</span>)</span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Tuple</span> (<span class="dt">Tuple</span>(..))</span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Validation</span> <span class="kw">as</span> <span class="dt">V</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a><span class="ot">renderForeignErrors ::</span> <span class="kw">forall</span> a<span class="op">.</span> <span class="dt">Either</span> (<span class="dt">NonEmptyList</span> <span class="dt">ForeignError</span>) a <span class="ot">-></span> <span class="dt">Either</span> <span class="dt">String</span> a</span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a>renderForeignErrors <span class="ot">=</span> lmap (toList <span class="op">>>></span> <span class="fu">map</span> renderForeignError <span class="op">>>></span> intercalate <span class="st">", "</span>)</span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a><span class="ot">updateUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a>updateUser pool <span class="ot">=</span> V.withValidation (<span class="dt">Tuple</span> <span class="op"><$></span> getUserId <span class="op"><*></span> getUserPatch)</span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a> \(<span class="dt">Tuple</span> userId (<span class="dt">UserPatch</span> userPatch)) <span class="ot">-></span></span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> unNullOrUndefined userPatch<span class="op">.</span>name <span class="kw">of</span></span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respondNoContent <span class="dv">204</span></span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> uName <span class="ot">-></span> V.withValidation (getUserName uName) \userName <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true" tabindex="-1"></a> savedUser <span class="ot"><-</span> liftAff <span class="op">$</span> PG.withConnection pool \conn <span class="ot">-></span> PG.withTransaction conn <span class="kw">do</span></span>
<span id="cb10-21"><a href="#cb10-21" aria-hidden="true" tabindex="-1"></a> P.findUser conn userId <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb10-22"><a href="#cb10-22" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">pure</span> <span class="dt">Nothing</span></span>
<span id="cb10-23"><a href="#cb10-23" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> (<span class="dt">User</span> user) <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb10-24"><a href="#cb10-24" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> user' <span class="ot">=</span> <span class="dt">User</span> (user { name <span class="ot">=</span> userName })</span>
<span id="cb10-25"><a href="#cb10-25" aria-hidden="true" tabindex="-1"></a> P.updateUser conn user'</span>
<span id="cb10-26"><a href="#cb10-26" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> <span class="op">$</span> <span class="dt">Just</span> user'</span>
<span id="cb10-27"><a href="#cb10-27" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> savedUser <span class="kw">of</span></span>
<span id="cb10-28"><a href="#cb10-28" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">404</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User not found with id: "</span> <span class="op"><></span> <span class="fu">show</span> userId }</span>
<span id="cb10-29"><a href="#cb10-29" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> user <span class="ot">-></span> respond <span class="dv">200</span> (encode user)</span>
<span id="cb10-30"><a href="#cb10-30" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb10-31"><a href="#cb10-31" aria-hidden="true" tabindex="-1"></a> getUserId <span class="ot">=</span> lift (getRouteParam <span class="st">"id"</span>)</span>
<span id="cb10-32"><a href="#cb10-32" aria-hidden="true" tabindex="-1"></a> <span class="op">>>=</span> V.exceptMaybe <span class="st">"User ID is required"</span></span>
<span id="cb10-33"><a href="#cb10-33" aria-hidden="true" tabindex="-1"></a> <span class="op">>>=</span> fromString <span class="op">>>></span> V.exceptMaybe <span class="st">"User ID must be positive"</span></span>
<span id="cb10-34"><a href="#cb10-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-35"><a href="#cb10-35" aria-hidden="true" tabindex="-1"></a> getUserPatch <span class="ot">=</span> lift getBody <span class="op">>>=</span> V.except <span class="op"><<<</span> renderForeignErrors</span>
<span id="cb10-36"><a href="#cb10-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-37"><a href="#cb10-37" aria-hidden="true" tabindex="-1"></a> getUserName <span class="ot">=</span> V.exceptCond <span class="st">"User name must not be empty"</span> (_ <span class="op">==</span> <span class="st">""</span>)</span></code></pre></div>
<p>The validation logic has been extracted out in separate functions now which are composed using <a href="https://pursuit.purescript.org/packages/purescript-prelude/3.0.0/docs/Control.Applicative#t:Applicative" target="_blank" rel="noopener">Applicative</a>. The validation steps are composed using the <code>ExceptT</code> monad. We are now free to express the core logic of the function clearly. We rewrite the <code>src/SimpleService/Handler.purs</code> file using the validations:</p>
<div class="sourceCode" id="cb11" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">SimpleService.Handler</span> <span class="kw">where</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Aff.Class</span> (liftAff)</span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Trans.Class</span> (lift)</span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Bifunctor</span> (lmap)</span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Either</span> (<span class="dt">Either</span>)</span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foldable</span> (intercalate)</span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foreign</span> (<span class="dt">ForeignError</span>, renderForeignError)</span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foreign.Class</span> (encode)</span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foreign.NullOrUndefined</span> (unNullOrUndefined)</span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Int</span> (fromString)</span>
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.List.NonEmpty</span> (toList)</span>
<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.List.Types</span> (<span class="dt">NonEmptyList</span>)</span>
<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Maybe</span> (<span class="dt">Maybe</span>(..))</span>
<span id="cb11-17"><a href="#cb11-17" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Tuple</span> (<span class="dt">Tuple</span>(..))</span>
<span id="cb11-18"><a href="#cb11-18" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Database.PostgreSQL</span> <span class="kw">as</span> <span class="dt">PG</span></span>
<span id="cb11-19"><a href="#cb11-19" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Handler</span> (<span class="dt">Handler</span>)</span>
<span id="cb11-20"><a href="#cb11-20" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Request</span> (getBody, getRouteParam)</span>
<span id="cb11-21"><a href="#cb11-21" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Response</span> (end, sendJson, setStatus)</span>
<span id="cb11-22"><a href="#cb11-22" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Persistence</span> <span class="kw">as</span> <span class="dt">P</span></span>
<span id="cb11-23"><a href="#cb11-23" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Validation</span> <span class="kw">as</span> <span class="dt">V</span></span>
<span id="cb11-24"><a href="#cb11-24" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Types</span></span>
<span id="cb11-25"><a href="#cb11-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-26"><a href="#cb11-26" aria-hidden="true" tabindex="-1"></a><span class="ot">getUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb11-27"><a href="#cb11-27" aria-hidden="true" tabindex="-1"></a>getUser pool <span class="ot">=</span> V.withValidation getUserId \userId <span class="ot">-></span></span>
<span id="cb11-28"><a href="#cb11-28" aria-hidden="true" tabindex="-1"></a> liftAff (PG.withConnection pool <span class="op">$</span> <span class="fu">flip</span> P.findUser userId) <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb11-29"><a href="#cb11-29" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">404</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User not found with id: "</span> <span class="op"><></span> <span class="fu">show</span> userId }</span>
<span id="cb11-30"><a href="#cb11-30" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> user <span class="ot">-></span> respond <span class="dv">200</span> (encode user)</span>
<span id="cb11-31"><a href="#cb11-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-32"><a href="#cb11-32" aria-hidden="true" tabindex="-1"></a><span class="ot">deleteUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb11-33"><a href="#cb11-33" aria-hidden="true" tabindex="-1"></a>deleteUser pool <span class="ot">=</span> V.withValidation getUserId \userId <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb11-34"><a href="#cb11-34" aria-hidden="true" tabindex="-1"></a> found <span class="ot"><-</span> liftAff <span class="op">$</span> PG.withConnection pool \conn <span class="ot">-></span> PG.withTransaction conn <span class="kw">do</span></span>
<span id="cb11-35"><a href="#cb11-35" aria-hidden="true" tabindex="-1"></a> P.findUser conn userId <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb11-36"><a href="#cb11-36" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">pure</span> false</span>
<span id="cb11-37"><a href="#cb11-37" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> _ <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb11-38"><a href="#cb11-38" aria-hidden="true" tabindex="-1"></a> P.deleteUser conn userId</span>
<span id="cb11-39"><a href="#cb11-39" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> true</span>
<span id="cb11-40"><a href="#cb11-40" aria-hidden="true" tabindex="-1"></a> <span class="kw">if</span> found</span>
<span id="cb11-41"><a href="#cb11-41" aria-hidden="true" tabindex="-1"></a> <span class="kw">then</span> respondNoContent <span class="dv">204</span></span>
<span id="cb11-42"><a href="#cb11-42" aria-hidden="true" tabindex="-1"></a> <span class="kw">else</span> respond <span class="dv">404</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User not found with id: "</span> <span class="op"><></span> <span class="fu">show</span> userId }</span>
<span id="cb11-43"><a href="#cb11-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-44"><a href="#cb11-44" aria-hidden="true" tabindex="-1"></a><span class="ot">createUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb11-45"><a href="#cb11-45" aria-hidden="true" tabindex="-1"></a>createUser pool <span class="ot">=</span> V.withValidation getUser \user<span class="op">@</span>(<span class="dt">User</span> _) <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb11-46"><a href="#cb11-46" aria-hidden="true" tabindex="-1"></a> liftAff (PG.withConnection pool <span class="op">$</span> <span class="fu">flip</span> P.insertUser user)</span>
<span id="cb11-47"><a href="#cb11-47" aria-hidden="true" tabindex="-1"></a> respondNoContent <span class="dv">201</span></span>
<span id="cb11-48"><a href="#cb11-48" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb11-49"><a href="#cb11-49" aria-hidden="true" tabindex="-1"></a> getUser <span class="ot">=</span> lift getBody</span>
<span id="cb11-50"><a href="#cb11-50" aria-hidden="true" tabindex="-1"></a> <span class="op">>>=</span> V.except <span class="op"><<<</span> renderForeignErrors</span>
<span id="cb11-51"><a href="#cb11-51" aria-hidden="true" tabindex="-1"></a> <span class="op">>>=</span> V.exceptCond <span class="st">"User ID must be positive"</span> (\(<span class="dt">User</span> user) <span class="ot">-></span> user<span class="op">.</span><span class="fu">id</span> <span class="op">></span> <span class="dv">0</span>)</span>
<span id="cb11-52"><a href="#cb11-52" aria-hidden="true" tabindex="-1"></a> <span class="op">>>=</span> V.exceptCond <span class="st">"User name must not be empty"</span> (\(<span class="dt">User</span> user) <span class="ot">-></span> user<span class="op">.</span>name <span class="op">/=</span> <span class="st">""</span>)</span>
<span id="cb11-53"><a href="#cb11-53" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-54"><a href="#cb11-54" aria-hidden="true" tabindex="-1"></a><span class="ot">updateUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb11-55"><a href="#cb11-55" aria-hidden="true" tabindex="-1"></a>updateUser pool <span class="ot">=</span> V.withValidation (<span class="dt">Tuple</span> <span class="op"><$></span> getUserId <span class="op"><*></span> getUserPatch)</span>
<span id="cb11-56"><a href="#cb11-56" aria-hidden="true" tabindex="-1"></a> \(<span class="dt">Tuple</span> userId (<span class="dt">UserPatch</span> userPatch)) <span class="ot">-></span></span>
<span id="cb11-57"><a href="#cb11-57" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> unNullOrUndefined userPatch<span class="op">.</span>name <span class="kw">of</span></span>
<span id="cb11-58"><a href="#cb11-58" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respondNoContent <span class="dv">204</span></span>
<span id="cb11-59"><a href="#cb11-59" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> uName <span class="ot">-></span> V.withValidation (getUserName uName) \userName <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb11-60"><a href="#cb11-60" aria-hidden="true" tabindex="-1"></a> savedUser <span class="ot"><-</span> liftAff <span class="op">$</span> PG.withConnection pool \conn <span class="ot">-></span> PG.withTransaction conn <span class="kw">do</span></span>
<span id="cb11-61"><a href="#cb11-61" aria-hidden="true" tabindex="-1"></a> P.findUser conn userId <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb11-62"><a href="#cb11-62" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">pure</span> <span class="dt">Nothing</span></span>
<span id="cb11-63"><a href="#cb11-63" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> (<span class="dt">User</span> user) <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb11-64"><a href="#cb11-64" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> user' <span class="ot">=</span> <span class="dt">User</span> (user { name <span class="ot">=</span> userName })</span>
<span id="cb11-65"><a href="#cb11-65" aria-hidden="true" tabindex="-1"></a> P.updateUser conn user'</span>
<span id="cb11-66"><a href="#cb11-66" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> <span class="op">$</span> <span class="dt">Just</span> user'</span>
<span id="cb11-67"><a href="#cb11-67" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> savedUser <span class="kw">of</span></span>
<span id="cb11-68"><a href="#cb11-68" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">404</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User not found with id: "</span> <span class="op"><></span> <span class="fu">show</span> userId }</span>
<span id="cb11-69"><a href="#cb11-69" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> user <span class="ot">-></span> respond <span class="dv">200</span> (encode user)</span>
<span id="cb11-70"><a href="#cb11-70" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb11-71"><a href="#cb11-71" aria-hidden="true" tabindex="-1"></a> getUserPatch <span class="ot">=</span> lift getBody <span class="op">>>=</span> V.except <span class="op"><<<</span> renderForeignErrors</span>
<span id="cb11-72"><a href="#cb11-72" aria-hidden="true" tabindex="-1"></a> getUserName <span class="ot">=</span> V.exceptCond <span class="st">"User name must not be empty"</span> (_ <span class="op">/=</span> <span class="st">""</span>)</span>
<span id="cb11-73"><a href="#cb11-73" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-74"><a href="#cb11-74" aria-hidden="true" tabindex="-1"></a><span class="ot">listUsers ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb11-75"><a href="#cb11-75" aria-hidden="true" tabindex="-1"></a>listUsers pool <span class="ot">=</span> liftAff (PG.withConnection pool P.listUsers) <span class="op">>>=</span> encode <span class="op">>>></span> respond <span class="dv">200</span></span>
<span id="cb11-76"><a href="#cb11-76" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-77"><a href="#cb11-77" aria-hidden="true" tabindex="-1"></a><span class="ot">getUserId ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">V.Validation</span> eff <span class="dt">Int</span></span>
<span id="cb11-78"><a href="#cb11-78" aria-hidden="true" tabindex="-1"></a>getUserId <span class="ot">=</span> lift (getRouteParam <span class="st">"id"</span>)</span>
<span id="cb11-79"><a href="#cb11-79" aria-hidden="true" tabindex="-1"></a> <span class="op">>>=</span> V.exceptMaybe <span class="st">"User ID is required"</span></span>
<span id="cb11-80"><a href="#cb11-80" aria-hidden="true" tabindex="-1"></a> <span class="op">>>=</span> fromString <span class="op">>>></span> V.exceptMaybe <span class="st">"User ID must be an integer"</span></span>
<span id="cb11-81"><a href="#cb11-81" aria-hidden="true" tabindex="-1"></a> <span class="op">>>=</span> V.exceptCond <span class="st">"User ID must be positive"</span> (_ <span class="op">></span> <span class="dv">0</span>)</span>
<span id="cb11-82"><a href="#cb11-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-83"><a href="#cb11-83" aria-hidden="true" tabindex="-1"></a><span class="ot">renderForeignErrors ::</span> <span class="kw">forall</span> a<span class="op">.</span> <span class="dt">Either</span> (<span class="dt">NonEmptyList</span> <span class="dt">ForeignError</span>) a <span class="ot">-></span> <span class="dt">Either</span> <span class="dt">String</span> a</span>
<span id="cb11-84"><a href="#cb11-84" aria-hidden="true" tabindex="-1"></a>renderForeignErrors <span class="ot">=</span> lmap (toList <span class="op">>>></span> <span class="fu">map</span> renderForeignError <span class="op">>>></span> intercalate <span class="st">", "</span>)</span>
<span id="cb11-85"><a href="#cb11-85" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-86"><a href="#cb11-86" aria-hidden="true" tabindex="-1"></a><span class="ot">respond ::</span> <span class="kw">forall</span> eff a<span class="op">.</span> <span class="dt">Int</span> <span class="ot">-></span> a <span class="ot">-></span> <span class="dt">Handler</span> eff</span>
<span id="cb11-87"><a href="#cb11-87" aria-hidden="true" tabindex="-1"></a>respond status body <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb11-88"><a href="#cb11-88" aria-hidden="true" tabindex="-1"></a> setStatus status</span>
<span id="cb11-89"><a href="#cb11-89" aria-hidden="true" tabindex="-1"></a> sendJson body</span>
<span id="cb11-90"><a href="#cb11-90" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-91"><a href="#cb11-91" aria-hidden="true" tabindex="-1"></a><span class="ot">respondNoContent ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">Int</span> <span class="ot">-></span> <span class="dt">Handler</span> eff</span>
<span id="cb11-92"><a href="#cb11-92" aria-hidden="true" tabindex="-1"></a>respondNoContent status <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb11-93"><a href="#cb11-93" aria-hidden="true" tabindex="-1"></a> setStatus status</span>
<span id="cb11-94"><a href="#cb11-94" aria-hidden="true" tabindex="-1"></a> end</span></code></pre></div>
<p>The code is much cleaner now. Let’s try out a few test cases:</p>
<pre class="plain"><code>$ http POST http://localhost:4000/v1/users id:=3 name=roger
HTTP/1.1 201 Created
Connection: keep-alive
Content-Length: 0
Date: Sat, 30 Sep 2017 12:13:37 GMT
X-Powered-By: Express</code></pre>
<pre class="plain"><code>$ http POST http://localhost:4000/v1/users id:=3
HTTP/1.1 422 Unprocessable Entity
Connection: keep-alive
Content-Length: 102
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Sep 2017 12:13:50 GMT
ETag: W/"66-/c4cfoquQZGwtDBUzHjJydJAHJ0"
X-Powered-By: Express
{
"error": "Error at array index 0: (ErrorAtProperty \"name\" (TypeMismatch \"String\" \"Undefined\"))"
}</code></pre>
<pre class="plain"><code>$ http POST http://localhost:4000/v1/users id:=3 name=""
HTTP/1.1 422 Unprocessable Entity
Connection: keep-alive
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Sep 2017 12:14:02 GMT
ETag: W/"27-JQsh12xu/rEFdWy8REF4NMtBUB4"
X-Powered-By: Express
{
"error": "User name must not be empty"
}</code></pre>
<pre class="plain"><code>$ http POST http://localhost:4000/v1/users id:=0 name=roger
HTTP/1.1 422 Unprocessable Entity
Connection: keep-alive
Content-Length: 36
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Sep 2017 12:14:14 GMT
ETag: W/"24-Pvt1L4eGilBmVtaOGHlSReJ413E"
X-Powered-By: Express
{
"error": "User ID must be positive"
}</code></pre>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/user/3
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 23
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Sep 2017 12:14:28 GMT
ETag: W/"17-1scpiB1FT9DBu9s4I1gNWSjH2go"
X-Powered-By: Express
{
"id": 3,
"name": "roger"
}</code></pre>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/user/asdf
HTTP/1.1 422 Unprocessable Entity
Connection: keep-alive
Content-Length: 38
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Sep 2017 12:14:40 GMT
ETag: W/"26-//tvORl1gGDUMwgSaqbEpJhuadI"
X-Powered-By: Express
{
"error": "User ID must be an integer"
}</code></pre>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/user/-1
HTTP/1.1 422 Unprocessable Entity
Connection: keep-alive
Content-Length: 36
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Sep 2017 12:14:45 GMT
ETag: W/"24-Pvt1L4eGilBmVtaOGHlSReJ413E"
X-Powered-By: Express
{
"error": "User ID must be positive"
}</code></pre>
<p>It works as expected.</p>
<h2 data-track-content data-content-name="configuration" data-content-piece="ps-simple-rest-service-2" id="configuration">Configuration</h2>
<p>Right now our application configuration resides in the <code>main</code> function:</p>
<div class="sourceCode" id="cb19" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a>main <span class="ot">=</span> runServer port databaseConfig</span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a> port <span class="ot">=</span> <span class="dv">4000</span></span>
<span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a> databaseConfig <span class="ot">=</span> { user<span class="op">:</span> <span class="st">"abhinav"</span></span>
<span id="cb19-5"><a href="#cb19-5" aria-hidden="true" tabindex="-1"></a> , password<span class="op">:</span> <span class="st">""</span></span>
<span id="cb19-6"><a href="#cb19-6" aria-hidden="true" tabindex="-1"></a> , host<span class="op">:</span> <span class="st">"localhost"</span></span>
<span id="cb19-7"><a href="#cb19-7" aria-hidden="true" tabindex="-1"></a> , port<span class="op">:</span> <span class="dv">5432</span></span>
<span id="cb19-8"><a href="#cb19-8" aria-hidden="true" tabindex="-1"></a> , database<span class="op">:</span> <span class="st">"simple_service"</span></span>
<span id="cb19-9"><a href="#cb19-9" aria-hidden="true" tabindex="-1"></a> , <span class="fu">max</span><span class="op">:</span> <span class="dv">10</span></span>
<span id="cb19-10"><a href="#cb19-10" aria-hidden="true" tabindex="-1"></a> , idleTimeoutMillis<span class="op">:</span> <span class="dv">1000</span></span>
<span id="cb19-11"><a href="#cb19-11" aria-hidden="true" tabindex="-1"></a> }</span></code></pre></div>
<p>We are going to extract it out of the code and read it from the environment variables using the <a href="https://pursuit.purescript.org/packages/purescript-config" target="_blank" rel="noopener"><code>purescript-config</code></a> package. First, we install the required packages using <a href="http://bower.io" target="_blank" rel="noopener">bower</a>.</p>
<pre class="plain"><code>$ bower install --save purescript-node-process purescript-config</code></pre>
<p>Now, we write the following code in the <code>src/SimpleService/Config.purs</code> file:</p>
<div class="sourceCode" id="cb21" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">SimpleService.Config</span> <span class="kw">where</span></span>
<span id="cb21-2"><a href="#cb21-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-3"><a href="#cb21-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Config</span></span>
<span id="cb21-4"><a href="#cb21-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb21-5"><a href="#cb21-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-6"><a href="#cb21-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff</span> (<span class="dt">Eff</span>)</span>
<span id="cb21-7"><a href="#cb21-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Config.Node</span> (fromEnv)</span>
<span id="cb21-8"><a href="#cb21-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Either</span> (<span class="dt">Either</span>)</span>
<span id="cb21-9"><a href="#cb21-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Set</span> (<span class="dt">Set</span>)</span>
<span id="cb21-10"><a href="#cb21-10" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Database.PostgreSQL</span> <span class="kw">as</span> <span class="dt">PG</span></span>
<span id="cb21-11"><a href="#cb21-11" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Process</span> (<span class="dt">PROCESS</span>)</span>
<span id="cb21-12"><a href="#cb21-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-13"><a href="#cb21-13" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">ServerConfig</span> <span class="ot">=</span></span>
<span id="cb21-14"><a href="#cb21-14" aria-hidden="true" tabindex="-1"></a> {<span class="ot"> port ::</span> <span class="dt">Int</span></span>
<span id="cb21-15"><a href="#cb21-15" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> databaseConfig ::</span> <span class="dt">PG.PoolConfiguration</span></span>
<span id="cb21-16"><a href="#cb21-16" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb21-17"><a href="#cb21-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-18"><a href="#cb21-18" aria-hidden="true" tabindex="-1"></a><span class="ot">databaseConfig ::</span> <span class="dt">Config</span> {<span class="ot">name ::</span> <span class="dt">String</span>} <span class="dt">PG.PoolConfiguration</span></span>
<span id="cb21-19"><a href="#cb21-19" aria-hidden="true" tabindex="-1"></a>databaseConfig <span class="ot">=</span></span>
<span id="cb21-20"><a href="#cb21-20" aria-hidden="true" tabindex="-1"></a> { user<span class="op">:</span> _, password<span class="op">:</span> _, host<span class="op">:</span> _, port<span class="op">:</span> _, database<span class="op">:</span> _, <span class="fu">max</span><span class="op">:</span> _, idleTimeoutMillis<span class="op">:</span> _ }</span>
<span id="cb21-21"><a href="#cb21-21" aria-hidden="true" tabindex="-1"></a> <span class="op"><$></span> string {name<span class="op">:</span> <span class="st">"user"</span>}</span>
<span id="cb21-22"><a href="#cb21-22" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> string {name<span class="op">:</span> <span class="st">"password"</span>}</span>
<span id="cb21-23"><a href="#cb21-23" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> string {name<span class="op">:</span> <span class="st">"host"</span>}</span>
<span id="cb21-24"><a href="#cb21-24" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> int {name<span class="op">:</span> <span class="st">"port"</span>}</span>
<span id="cb21-25"><a href="#cb21-25" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> string {name<span class="op">:</span> <span class="st">"database"</span>}</span>
<span id="cb21-26"><a href="#cb21-26" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> int {name<span class="op">:</span> <span class="st">"pool_size"</span>}</span>
<span id="cb21-27"><a href="#cb21-27" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> int {name<span class="op">:</span> <span class="st">"idle_conn_timeout_millis"</span>}</span>
<span id="cb21-28"><a href="#cb21-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-29"><a href="#cb21-29" aria-hidden="true" tabindex="-1"></a><span class="ot">portConfig ::</span> <span class="dt">Config</span> {<span class="ot">name ::</span> <span class="dt">String</span>} <span class="dt">Int</span></span>
<span id="cb21-30"><a href="#cb21-30" aria-hidden="true" tabindex="-1"></a>portConfig <span class="ot">=</span> int {name<span class="op">:</span> <span class="st">"port"</span>}</span>
<span id="cb21-31"><a href="#cb21-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-32"><a href="#cb21-32" aria-hidden="true" tabindex="-1"></a><span class="ot">serverConfig ::</span> <span class="dt">Config</span> {<span class="ot">name ::</span> <span class="dt">String</span>} <span class="dt">ServerConfig</span></span>
<span id="cb21-33"><a href="#cb21-33" aria-hidden="true" tabindex="-1"></a>serverConfig <span class="ot">=</span></span>
<span id="cb21-34"><a href="#cb21-34" aria-hidden="true" tabindex="-1"></a> { port<span class="op">:</span> _, databaseConfig<span class="op">:</span> _}</span>
<span id="cb21-35"><a href="#cb21-35" aria-hidden="true" tabindex="-1"></a> <span class="op"><$></span> portConfig</span>
<span id="cb21-36"><a href="#cb21-36" aria-hidden="true" tabindex="-1"></a> <span class="op"><*></span> prefix {name<span class="op">:</span> <span class="st">"db"</span>} databaseConfig</span>
<span id="cb21-37"><a href="#cb21-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-38"><a href="#cb21-38" aria-hidden="true" tabindex="-1"></a><span class="ot">readServerConfig ::</span> <span class="kw">forall</span> eff<span class="op">.</span></span>
<span id="cb21-39"><a href="#cb21-39" aria-hidden="true" tabindex="-1"></a> <span class="dt">Eff</span> (<span class="ot">process ::</span> <span class="dt">PROCESS</span> <span class="op">|</span> eff) (<span class="dt">Either</span> (<span class="dt">Set</span> <span class="dt">String</span>) <span class="dt">ServerConfig</span>)</span>
<span id="cb21-40"><a href="#cb21-40" aria-hidden="true" tabindex="-1"></a>readServerConfig <span class="ot">=</span> fromEnv <span class="st">"SS"</span> serverConfig</span></code></pre></div>
<p>We use the applicative DSL provided in <code>Data.Config</code> module to build a description of our configuration. This description contains the keys and types of the configuration, for consumption by various interpreters. Then we use the <code>fromEnv</code> interpreter to read the config from the environment variables derived from the <code>name</code> fields in the records in the description in the <code>readServerConfig</code> function. We also write a bash script to set those environment variables in the development environment in the <code>setenv.sh</code> file:</p>
<div class="sourceCode" id="cb22" data-lang="bash"><pre class="sourceCode numberSource bash"><code class="sourceCode bash"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true" tabindex="-1"></a><span class="bu">export</span> <span class="va">SS_PORT</span><span class="op">=</span>4000</span>
<span id="cb22-2"><a href="#cb22-2" aria-hidden="true" tabindex="-1"></a><span class="bu">export</span> <span class="va">SS_DB_USER</span><span class="op">=</span><span class="st">"abhinav"</span></span>
<span id="cb22-3"><a href="#cb22-3" aria-hidden="true" tabindex="-1"></a><span class="bu">export</span> <span class="va">SS_DB_PASSWORD</span><span class="op">=</span><span class="st">""</span></span>
<span id="cb22-4"><a href="#cb22-4" aria-hidden="true" tabindex="-1"></a><span class="bu">export</span> <span class="va">SS_DB_HOST</span><span class="op">=</span><span class="st">"localhost"</span></span>
<span id="cb22-5"><a href="#cb22-5" aria-hidden="true" tabindex="-1"></a><span class="bu">export</span> <span class="va">SS_DB_PORT</span><span class="op">=</span>5432</span>
<span id="cb22-6"><a href="#cb22-6" aria-hidden="true" tabindex="-1"></a><span class="bu">export</span> <span class="va">SS_DB_DATABASE</span><span class="op">=</span><span class="st">"simple_service"</span></span>
<span id="cb22-7"><a href="#cb22-7" aria-hidden="true" tabindex="-1"></a><span class="bu">export</span> <span class="va">SS_DB_POOL_SIZE</span><span class="op">=</span>10</span>
<span id="cb22-8"><a href="#cb22-8" aria-hidden="true" tabindex="-1"></a><span class="bu">export</span> <span class="va">SS_DB_IDLE_CONN_TIMEOUT_MILLIS</span><span class="op">=</span>1000</span></code></pre></div>
<p>Now we rewrite our <code>src/Main.purs</code> file to use the <code>readServerConfig</code> function:</p>
<div class="sourceCode" id="cb23" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb23-1"><a href="#cb23-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">Main</span> <span class="kw">where</span></span>
<span id="cb23-2"><a href="#cb23-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-3"><a href="#cb23-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb23-4"><a href="#cb23-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-5"><a href="#cb23-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff</span> (<span class="dt">Eff</span>)</span>
<span id="cb23-6"><a href="#cb23-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff.Console</span> (<span class="dt">CONSOLE</span>, log)</span>
<span id="cb23-7"><a href="#cb23-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Either</span> (<span class="dt">Either</span>(..))</span>
<span id="cb23-8"><a href="#cb23-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Set</span> (toUnfoldable)</span>
<span id="cb23-9"><a href="#cb23-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.String</span> (joinWith)</span>
<span id="cb23-10"><a href="#cb23-10" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Database.PostgreSQL</span> <span class="kw">as</span> <span class="dt">PG</span></span>
<span id="cb23-11"><a href="#cb23-11" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Types</span> (<span class="dt">EXPRESS</span>)</span>
<span id="cb23-12"><a href="#cb23-12" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Process</span> (<span class="dt">PROCESS</span>)</span>
<span id="cb23-13"><a href="#cb23-13" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Process</span> <span class="kw">as</span> <span class="dt">Process</span></span>
<span id="cb23-14"><a href="#cb23-14" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Config</span> (readServerConfig)</span>
<span id="cb23-15"><a href="#cb23-15" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Server</span> (runServer)</span>
<span id="cb23-16"><a href="#cb23-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb23-17"><a href="#cb23-17" aria-hidden="true" tabindex="-1"></a><span class="ot">main ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">Eff</span> (<span class="ot"> console ::</span> <span class="dt">CONSOLE</span></span>
<span id="cb23-18"><a href="#cb23-18" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> express ::</span> <span class="dt">EXPRESS</span></span>
<span id="cb23-19"><a href="#cb23-19" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span></span>
<span id="cb23-20"><a href="#cb23-20" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> process ::</span> <span class="dt">PROCESS</span></span>
<span id="cb23-21"><a href="#cb23-21" aria-hidden="true" tabindex="-1"></a> <span class="op">|</span> eff ) <span class="dt">Unit</span></span>
<span id="cb23-22"><a href="#cb23-22" aria-hidden="true" tabindex="-1"></a>main <span class="ot">=</span> readServerConfig <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb23-23"><a href="#cb23-23" aria-hidden="true" tabindex="-1"></a> <span class="dt">Left</span> missingKeys <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb23-24"><a href="#cb23-24" aria-hidden="true" tabindex="-1"></a> <span class="fu">log</span> <span class="op">$</span> <span class="st">"Unable to start. Missing Env keys: "</span> <span class="op"><></span> joinWith <span class="st">", "</span> (toUnfoldable missingKeys)</span>
<span id="cb23-25"><a href="#cb23-25" aria-hidden="true" tabindex="-1"></a> Process.exit <span class="dv">1</span></span>
<span id="cb23-26"><a href="#cb23-26" aria-hidden="true" tabindex="-1"></a> <span class="dt">Right</span> { port, databaseConfig } <span class="ot">-></span> runServer port databaseConfig</span></code></pre></div>
<p>If <code>readServerConfig</code> fails, we print the missing keys to the console and exit the process. Else we run the server with the read config.</p>
<p>To test this, we stop the server we ran in the beginning, source the config, and run it again:</p>
<pre class="plain"><code>$ pulp --watch run
* Building project in /Users/abhinav/ps-simple-rest-service
* Build successful.
Server listening on :4000
^C
$ source setenv.sh
$ pulp --watch run
* Building project in /Users/abhinav/ps-simple-rest-service
* Build successful.
Server listening on :4000</code></pre>
<p>It works! We test the failure case by opening another terminal which does not have the environment variables set:</p>
<pre class="plain"><code>$ pulp run
* Building project in /Users/abhinav/ps-simple-rest-service
* Build successful.
Unable to start. Missing Env keys: SS_DB_DATABASE, SS_DB_HOST, SS_DB_IDLE_CONN_TIMEOUT_MILLIS, SS_DB_PASSWORD, SS_DB_POOL_SIZE, SS_DB_PORT, SS_DB_USER, SS_PORT
* ERROR: Subcommand terminated with exit code 1</code></pre>
<p>Up next, we add logging to our application.</p>
<h2 data-track-content data-content-name="logging" data-content-piece="ps-simple-rest-service-2" id="logging">Logging</h2>
<p>For logging, we use the <a href="https://pursuit.purescript.org/packages/purescript-logging" target="_blank" rel="noopener"><code>purescript-logging</code></a> package. We write a logger which logs to <code>stdout</code>; in the <code>src/SimpleService/Logger.purs</code> file:</p>
<div class="sourceCode" id="cb26" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb26-1"><a href="#cb26-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">SimpleService.Logger</span></span>
<span id="cb26-2"><a href="#cb26-2" aria-hidden="true" tabindex="-1"></a> ( debug</span>
<span id="cb26-3"><a href="#cb26-3" aria-hidden="true" tabindex="-1"></a> , info</span>
<span id="cb26-4"><a href="#cb26-4" aria-hidden="true" tabindex="-1"></a> , warn</span>
<span id="cb26-5"><a href="#cb26-5" aria-hidden="true" tabindex="-1"></a> , <span class="fu">error</span></span>
<span id="cb26-6"><a href="#cb26-6" aria-hidden="true" tabindex="-1"></a> ) <span class="kw">where</span></span>
<span id="cb26-7"><a href="#cb26-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-8"><a href="#cb26-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb26-9"><a href="#cb26-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-10"><a href="#cb26-10" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Logger</span> <span class="kw">as</span> <span class="dt">L</span></span>
<span id="cb26-11"><a href="#cb26-11" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff.Class</span> (class <span class="dt">MonadEff</span>, liftEff)</span>
<span id="cb26-12"><a href="#cb26-12" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff.Console</span> <span class="kw">as</span> <span class="dt">C</span></span>
<span id="cb26-13"><a href="#cb26-13" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff.Now</span> (<span class="dt">NOW</span>, now)</span>
<span id="cb26-14"><a href="#cb26-14" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.DateTime.Instant</span> (toDateTime)</span>
<span id="cb26-15"><a href="#cb26-15" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Either</span> (fromRight)</span>
<span id="cb26-16"><a href="#cb26-16" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Formatter.DateTime</span> (<span class="dt">Formatter</span>, format, parseFormatString)</span>
<span id="cb26-17"><a href="#cb26-17" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Generic.Rep</span> (class <span class="dt">Generic</span>)</span>
<span id="cb26-18"><a href="#cb26-18" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Generic.Rep.Show</span> (genericShow)</span>
<span id="cb26-19"><a href="#cb26-19" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.String</span> (toUpper)</span>
<span id="cb26-20"><a href="#cb26-20" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Partial.Unsafe</span> (unsafePartial)</span>
<span id="cb26-21"><a href="#cb26-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-22"><a href="#cb26-22" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Level</span> <span class="ot">=</span> <span class="dt">Debug</span> <span class="op">|</span> <span class="dt">Info</span> <span class="op">|</span> <span class="dt">Warn</span> <span class="op">|</span> <span class="dt">Error</span></span>
<span id="cb26-23"><a href="#cb26-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-24"><a href="#cb26-24" aria-hidden="true" tabindex="-1"></a>derive <span class="kw">instance</span><span class="ot"> eqLevel ::</span> <span class="dt">Eq</span> <span class="dt">Level</span></span>
<span id="cb26-25"><a href="#cb26-25" aria-hidden="true" tabindex="-1"></a>derive <span class="kw">instance</span><span class="ot"> ordLevel ::</span> <span class="dt">Ord</span> <span class="dt">Level</span></span>
<span id="cb26-26"><a href="#cb26-26" aria-hidden="true" tabindex="-1"></a>derive <span class="kw">instance</span><span class="ot"> genericLevel ::</span> <span class="dt">Generic</span> <span class="dt">Level</span> _</span>
<span id="cb26-27"><a href="#cb26-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-28"><a href="#cb26-28" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span><span class="ot"> showLevel ::</span> <span class="dt">Show</span> <span class="dt">Level</span> <span class="kw">where</span></span>
<span id="cb26-29"><a href="#cb26-29" aria-hidden="true" tabindex="-1"></a> <span class="fu">show</span> <span class="ot">=</span> <span class="fu">toUpper</span> <span class="op"><<<</span> genericShow</span>
<span id="cb26-30"><a href="#cb26-30" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-31"><a href="#cb26-31" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">Entry</span> <span class="ot">=</span></span>
<span id="cb26-32"><a href="#cb26-32" aria-hidden="true" tabindex="-1"></a> {<span class="ot"> level ::</span> <span class="dt">Level</span></span>
<span id="cb26-33"><a href="#cb26-33" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> message ::</span> <span class="dt">String</span></span>
<span id="cb26-34"><a href="#cb26-34" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb26-35"><a href="#cb26-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-36"><a href="#cb26-36" aria-hidden="true" tabindex="-1"></a><span class="ot">dtFormatter ::</span> <span class="dt">Formatter</span></span>
<span id="cb26-37"><a href="#cb26-37" aria-hidden="true" tabindex="-1"></a>dtFormatter <span class="ot">=</span> unsafePartial <span class="op">$</span> fromRight <span class="op">$</span> parseFormatString <span class="st">"YYYY-MM-DD HH:mm:ss.SSS"</span></span>
<span id="cb26-38"><a href="#cb26-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-39"><a href="#cb26-39" aria-hidden="true" tabindex="-1"></a><span class="ot">logger ::</span> <span class="kw">forall</span> m e<span class="op">.</span> (</span>
<span id="cb26-40"><a href="#cb26-40" aria-hidden="true" tabindex="-1"></a> <span class="dt">MonadEff</span> (<span class="ot">console ::</span> <span class="dt">C.CONSOLE</span>,<span class="ot"> now ::</span> <span class="dt">NOW</span> <span class="op">|</span> e) m) <span class="ot">=></span> <span class="dt">L.Logger</span> m <span class="dt">Entry</span></span>
<span id="cb26-41"><a href="#cb26-41" aria-hidden="true" tabindex="-1"></a>logger <span class="ot">=</span> <span class="dt">L.Logger</span> <span class="op">$</span> \{ level, message } <span class="ot">-></span> liftEff <span class="kw">do</span></span>
<span id="cb26-42"><a href="#cb26-42" aria-hidden="true" tabindex="-1"></a> time <span class="ot"><-</span> toDateTime <span class="op"><$></span> now</span>
<span id="cb26-43"><a href="#cb26-43" aria-hidden="true" tabindex="-1"></a> C.log <span class="op">$</span> <span class="st">"["</span> <span class="op"><></span> format dtFormatter time <span class="op"><></span> <span class="st">"] "</span> <span class="op"><></span> <span class="fu">show</span> level <span class="op"><></span> <span class="st">" "</span> <span class="op"><></span> message</span>
<span id="cb26-44"><a href="#cb26-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-45"><a href="#cb26-45" aria-hidden="true" tabindex="-1"></a><span class="fu">log</span><span class="ot"> ::</span> <span class="kw">forall</span> m e<span class="op">.</span></span>
<span id="cb26-46"><a href="#cb26-46" aria-hidden="true" tabindex="-1"></a> <span class="dt">MonadEff</span> (<span class="ot">console ::</span> <span class="dt">C.CONSOLE</span> ,<span class="ot"> now ::</span> <span class="dt">NOW</span> <span class="op">|</span> e) m</span>
<span id="cb26-47"><a href="#cb26-47" aria-hidden="true" tabindex="-1"></a> <span class="ot">=></span> <span class="dt">Entry</span> <span class="ot">-></span> m <span class="dt">Unit</span></span>
<span id="cb26-48"><a href="#cb26-48" aria-hidden="true" tabindex="-1"></a><span class="fu">log</span> entry<span class="op">@</span>{level} <span class="ot">=</span> L.log (L.cfilter (\e <span class="ot">-></span> e<span class="op">.</span>level <span class="op">==</span> level) logger) entry</span>
<span id="cb26-49"><a href="#cb26-49" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-50"><a href="#cb26-50" aria-hidden="true" tabindex="-1"></a><span class="ot">debug ::</span> <span class="kw">forall</span> m e<span class="op">.</span></span>
<span id="cb26-51"><a href="#cb26-51" aria-hidden="true" tabindex="-1"></a> <span class="dt">MonadEff</span> (<span class="ot">console ::</span> <span class="dt">C.CONSOLE</span> ,<span class="ot"> now ::</span> <span class="dt">NOW</span> <span class="op">|</span> e) m <span class="ot">=></span> <span class="dt">String</span> <span class="ot">-></span> m <span class="dt">Unit</span></span>
<span id="cb26-52"><a href="#cb26-52" aria-hidden="true" tabindex="-1"></a>debug message <span class="ot">=</span> <span class="fu">log</span> { level<span class="op">:</span> <span class="dt">Debug</span>, message }</span>
<span id="cb26-53"><a href="#cb26-53" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-54"><a href="#cb26-54" aria-hidden="true" tabindex="-1"></a><span class="ot">info ::</span> <span class="kw">forall</span> m e<span class="op">.</span></span>
<span id="cb26-55"><a href="#cb26-55" aria-hidden="true" tabindex="-1"></a> <span class="dt">MonadEff</span> (<span class="ot">console ::</span> <span class="dt">C.CONSOLE</span> ,<span class="ot"> now ::</span> <span class="dt">NOW</span> <span class="op">|</span> e) m <span class="ot">=></span> <span class="dt">String</span> <span class="ot">-></span> m <span class="dt">Unit</span></span>
<span id="cb26-56"><a href="#cb26-56" aria-hidden="true" tabindex="-1"></a>info message <span class="ot">=</span> <span class="fu">log</span> { level<span class="op">:</span> <span class="dt">Info</span>, message }</span>
<span id="cb26-57"><a href="#cb26-57" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-58"><a href="#cb26-58" aria-hidden="true" tabindex="-1"></a><span class="ot">warn ::</span> <span class="kw">forall</span> m e<span class="op">.</span></span>
<span id="cb26-59"><a href="#cb26-59" aria-hidden="true" tabindex="-1"></a> <span class="dt">MonadEff</span> (<span class="ot">console ::</span> <span class="dt">C.CONSOLE</span> ,<span class="ot"> now ::</span> <span class="dt">NOW</span> <span class="op">|</span> e) m <span class="ot">=></span> <span class="dt">String</span> <span class="ot">-></span> m <span class="dt">Unit</span></span>
<span id="cb26-60"><a href="#cb26-60" aria-hidden="true" tabindex="-1"></a>warn message <span class="ot">=</span> <span class="fu">log</span> { level<span class="op">:</span> <span class="dt">Warn</span>, message }</span>
<span id="cb26-61"><a href="#cb26-61" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-62"><a href="#cb26-62" aria-hidden="true" tabindex="-1"></a><span class="fu">error</span><span class="ot"> ::</span> <span class="kw">forall</span> m e<span class="op">.</span></span>
<span id="cb26-63"><a href="#cb26-63" aria-hidden="true" tabindex="-1"></a> <span class="dt">MonadEff</span> (<span class="ot">console ::</span> <span class="dt">C.CONSOLE</span> ,<span class="ot"> now ::</span> <span class="dt">NOW</span> <span class="op">|</span> e) m <span class="ot">=></span> <span class="dt">String</span> <span class="ot">-></span> m <span class="dt">Unit</span></span>
<span id="cb26-64"><a href="#cb26-64" aria-hidden="true" tabindex="-1"></a><span class="fu">error</span> message <span class="ot">=</span> <span class="fu">log</span> { level<span class="op">:</span> <span class="dt">Error</span>, message }</span></code></pre></div>
<p><code>purescript-logging</code> lets us define our own logging levels and loggers. We define four log levels, and a log entry type with the log level and the message. Then we write the logger which will print the log entry to <code>stdout</code> along with the current time as a well formatted string. We define convenience functions for each log level.</p>
<p>Before we proceed, let’s install the required dependencies.</p>
<pre class="numberSource terminal"><code>$ bower install --save purescript-logging purescript-now purescript-formatters</code></pre>
<p>Now we add a request logger middleware to our server in the <code>src/SimpleService/Server.purs</code> file:</p>
<div class="sourceCode" id="cb28" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb28-1"><a href="#cb28-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb28-2"><a href="#cb28-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff.Console</span> (<span class="dt">CONSOLE</span>)</span>
<span id="cb28-3"><a href="#cb28-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff.Now</span> (<span class="dt">NOW</span>)</span>
<span id="cb28-4"><a href="#cb28-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Maybe</span> (maybe)</span>
<span id="cb28-5"><a href="#cb28-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.String</span> (toUpper)</span>
<span id="cb28-6"><a href="#cb28-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.App</span> (<span class="dt">App</span>, all, delete, get, http, listenHttp, post, use, useExternal, useOnError)</span>
<span id="cb28-7"><a href="#cb28-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Handler</span> (<span class="dt">Handler</span>, next)</span>
<span id="cb28-8"><a href="#cb28-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Request</span> (getMethod, getPath)</span>
<span id="cb28-9"><a href="#cb28-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Logger</span> <span class="kw">as</span> <span class="dt">Log</span></span>
<span id="cb28-10"><a href="#cb28-10" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb28-11"><a href="#cb28-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb28-12"><a href="#cb28-12" aria-hidden="true" tabindex="-1"></a><span class="ot">requestLogger ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">Handler</span> (<span class="ot">console ::</span> <span class="dt">CONSOLE</span>,<span class="ot"> now ::</span> <span class="dt">NOW</span> <span class="op">|</span> eff)</span>
<span id="cb28-13"><a href="#cb28-13" aria-hidden="true" tabindex="-1"></a>requestLogger <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb28-14"><a href="#cb28-14" aria-hidden="true" tabindex="-1"></a> method <span class="ot"><-</span> getMethod</span>
<span id="cb28-15"><a href="#cb28-15" aria-hidden="true" tabindex="-1"></a> path <span class="ot"><-</span> getPath</span>
<span id="cb28-16"><a href="#cb28-16" aria-hidden="true" tabindex="-1"></a> Log.debug <span class="op">$</span> <span class="st">"HTTP: "</span> <span class="op"><></span> <span class="fu">maybe</span> <span class="st">""</span> <span class="fu">id</span> ((<span class="fu">toUpper</span> <span class="op"><<<</span> <span class="fu">show</span>) <span class="op"><$></span> method) <span class="op"><></span> <span class="st">" "</span> <span class="op"><></span> path</span>
<span id="cb28-17"><a href="#cb28-17" aria-hidden="true" tabindex="-1"></a> next</span>
<span id="cb28-18"><a href="#cb28-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb28-19"><a href="#cb28-19" aria-hidden="true" tabindex="-1"></a><span class="ot">app ::</span> <span class="kw">forall</span> eff<span class="op">.</span></span>
<span id="cb28-20"><a href="#cb28-20" aria-hidden="true" tabindex="-1"></a> <span class="dt">PG.Pool</span></span>
<span id="cb28-21"><a href="#cb28-21" aria-hidden="true" tabindex="-1"></a> <span class="ot">-></span> <span class="dt">App</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span>,<span class="ot"> console ::</span> <span class="dt">CONSOLE</span>,<span class="ot"> now ::</span> <span class="dt">NOW</span> <span class="op">|</span> eff)</span>
<span id="cb28-22"><a href="#cb28-22" aria-hidden="true" tabindex="-1"></a>app pool <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb28-23"><a href="#cb28-23" aria-hidden="true" tabindex="-1"></a> useExternal jsonBodyParser</span>
<span id="cb28-24"><a href="#cb28-24" aria-hidden="true" tabindex="-1"></a> use requestLogger</span>
<span id="cb28-25"><a href="#cb28-25" aria-hidden="true" tabindex="-1"></a> <span class="co">-- previous code</span></span></code></pre></div>
<p>We also convert all our previous logging statements which used <code>Console.log</code> to use <code>SimpleService.Logger</code> and add logs in our handlers. We can see logging in effect by restarting the server and hitting it:</p>
<pre class="plain"><code>$ pulp --watch run
* Building project in /Users/abhinav/ps-simple-rest-service
* Build successful.
[2017-09-30 16:02:41.634] INFO Server listening on :4000
[2017-09-30 16:02:43.494] DEBUG HTTP: PATCH /v1/user/3
[2017-09-30 16:02:43.517] DEBUG Updated user: 3
[2017-09-30 16:03:46.615] DEBUG HTTP: DELETE /v1/user/3
[2017-09-30 16:03:46.635] DEBUG Deleted user 3
[2017-09-30 16:05:03.805] DEBUG HTTP: GET /v1/users</code></pre>
<h2 data-track-content data-content-name="conclusion" data-content-piece="ps-simple-rest-service-2" id="conclusion">Conclusion</h2>
<p>In this tutorial we learned how to create a simple JSON REST web service written in PureScript with persistence, validation, configuration and logging. The complete code for this tutorial can be found in <a href="https://github.com/abhin4v/ps-simple-rest-service" target="_blank" rel="noopener">github</a>.</p><p>If you liked this post, please <a href="https://abhinavsarkar.net/posts/ps-simple-rest-service-2/?mtm_campaign=feed#syndications">leave a comment</a>.</p><img referrerpolicy="no-referrer-when-downgrade" src="https://anna.abhinavsarkar.net/matomo.php?idsite=1&rec=1" style="border:0" alt="" />2017-10-01T00:00:00Z<p>To recap, in the <a href="https://abhinavsarkar.net/posts/ps-simple-rest-service/">first</a> part of this two-part tutorial, we built a simple JSON <a href="https://en.wikipedia.org/wiki/REST" target="_blank" rel="noopener">REST</a> web service in <a href="http://purescript.org" target="_blank" rel="noopener">PureScript</a> to create, update, get, list and delete users, backed by a Postgres database. In this part we’ll work on the rest of the features. https://abhinavsarkar.net/posts/ps-simple-rest-service/Writing a Simple REST Web Service in PureScript—Part 12017-09-29T00:00:00ZAbhinav Sarkarhttps://abhinavsarkar.net/about/abhinav@abhinavsarkar.net<p>At <a href="https://nilenso.com" target="_blank" rel="noopener">Nilenso</a>, we’ve been working with a client who has chosen <a href="http://purescript.org" target="_blank" rel="noopener">PureScript</a> as their primary programming language. Since I couldn’t find any canonical documentation on writing a web service in PureScript, I thought I’d jot down the approach that we took.</p>
<p>The aim of this two-part tutorial is to create a simple JSON <a href="https://en.wikipedia.org/wiki/REST" target="_blank" rel="noopener">REST</a> web service written in PureScript, to run on a node.js server. <p>This post was originally published on <a href="https://abhinavsarkar.net/posts/ps-simple-rest-service/?mtm_campaign=feed">abhinavsarkar.net</a>.</p><!--more--> This assumes that you have basic proficiency with PureScript. We have the following requirements:</p>
<ol type="1">
<li>persisting users into a Postgres database.</li>
<li>API endpoints for creating, updating, getting, listing and deleting users.</li>
<li>validation of API requests.</li>
<li>reading the server and database configs from environment variables.</li>
<li>logging HTTP requests and debugging info.</li>
</ol>
<p>In this part we’ll work on setting up the project and on the first two requirements. In the <a href="https://abhinavsarkar.net/posts/ps-simple-rest-service-2/?mtm_campaign=feed">next</a> part we’ll work on the rest of the requirements.</p>
<nav id="toc" class="right-toc"><h3>Contents</h3><ol><li><a href="#setting-up">Setting Up</a></li><li><a href="#types-first">Types First</a></li><li><a href="#persisting-it">Persisting It</a></li><li><a href="#serving-it">Serving It</a><ol><li><a href="#getting-a-user">Getting a User</a></li><li><a href="#deleting-a-user">Deleting a User</a></li><li><a href="#creating-a-user">Creating a User</a></li><li><a href="#updating-a-user">Updating a User</a></li><li><a href="#listing-all-users">Listing all Users</a></li></ol></li><li><a href="#conclusion">Conclusion</a></li></ol></nav>
<h2 data-track-content data-content-name="setting-up" data-content-piece="ps-simple-rest-service" id="setting-up">Setting Up</h2>
<p>We start with installing PureScript and the required tools. This assumes that we have <a href="https://nodejs.org" target="_blank" rel="noopener">node</a> and <a href="https://www.npmjs.com" target="_blank" rel="noopener">npm</a> installed on our machine.</p>
<pre class="plain"><code>$ mkdir -p ~/.local/
$ npm install -g purescript pulp bower --prefix ~/.local/</code></pre>
<p><a href="https://github.com/purescript-contrib/pulp" target="_blank" rel="noopener">Pulp</a> is a build tool for PureScript projects and <a href="http://bower.io" target="_blank" rel="noopener">bower</a> is a package manager used to get PureScript libraries. We’ll have to add <code>~/.local/bin</code> in our <code>$PATH</code> (if it is not already added) to access the binaries installed.</p>
<p>Let’s create a directory for our project and make Pulp initialize it:</p>
<pre class="plain"><code>$ mkdir ps-simple-rest-service
$ cd ps-simple-rest-service
$ pulp init
$ ls
bower.json bower_components src test
$ cat bower.json
{
"name": "ps-simple-rest-service",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"output"
],
"dependencies": {
"purescript-prelude": "^3.1.0",
"purescript-console": "^3.0.0"
},
"devDependencies": {
"purescript-psci-support": "^3.0.0"
}
}
$ ls bower_components
purescript-console purescript-eff purescript-prelude purescript-psci-support</code></pre>
<p>Pulp creates the basic project structure for us. <code>src</code> directory will contain the source while the <code>test</code> directory will contain the tests. <code>bower.json</code> contains the PureScript libraries as dependencies which are downloaded and installed in the <code>bower_components</code> directory.</p>
<div class="page-break">
</div>
<h2 data-track-content data-content-name="types-first" data-content-piece="ps-simple-rest-service" id="types-first">Types First</h2>
<p>First, we create the types needed in <code>src/SimpleService/Types.purs</code>:</p>
<div class="sourceCode" id="cb3" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">SimpleService.Types</span> <span class="kw">where</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foreign.Class</span> (class <span class="dt">Decode</span>, class <span class="dt">Encode</span>)</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foreign.Generic</span> (defaultOptions, genericDecode, genericEncode)</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Generic.Rep</span> (class <span class="dt">Generic</span>)</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Generic.Rep.Show</span> (genericShow)</span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">UserID</span> <span class="ot">=</span> <span class="dt">Int</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">User</span> <span class="ot">=</span> <span class="dt">User</span></span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a> {<span class="ot"> id ::</span> <span class="dt">UserID</span></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> name ::</span> <span class="dt">String</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a>derive <span class="kw">instance</span><span class="ot"> genericUser ::</span> <span class="dt">Generic</span> <span class="dt">User</span> _</span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span><span class="ot"> showUser ::</span> <span class="dt">Show</span> <span class="dt">User</span> <span class="kw">where</span></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a> <span class="fu">show</span> <span class="ot">=</span> genericShow</span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span><span class="ot"> decodeUser ::</span> <span class="dt">Decode</span> <span class="dt">User</span> <span class="kw">where</span></span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a> decode <span class="ot">=</span> genericDecode <span class="op">$</span> defaultOptions { unwrapSingleConstructors <span class="ot">=</span> true }</span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span><span class="ot"> encodeUser ::</span> <span class="dt">Encode</span> <span class="dt">User</span> <span class="kw">where</span></span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a> encode <span class="ot">=</span> genericEncode <span class="op">$</span> defaultOptions { unwrapSingleConstructors <span class="ot">=</span> true }</span></code></pre></div>
<p>We are using the generic support for PureScript types from the <a href="https://pursuit.purescript.org/packages/purescript-generics-rep" target="_blank" rel="noopener"><code>purescript-generics-rep</code></a> and <a href="https://pursuit.purescript.org/packages/purescript-foreign-generic" target="_blank" rel="noopener"><code>purescript-foreign-generic</code></a> libraries to encode and decode the <code>User</code> type to JSON. We install the library by running the following command:</p>
<pre class="plain"><code>$ bower install purescript-foreign-generic --save</code></pre>
<p>Now we can load up the module in the PureScript REPL and try out the JSON conversion features:</p>
<div class="sourceCode" id="cb5" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="op">$</span> pulp repl</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">import</span> <span class="dt">SimpleService.Types</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="op">></span> user <span class="ot">=</span> <span class="dt">User</span> { <span class="fu">id</span><span class="op">:</span> <span class="dv">1</span>, name<span class="op">:</span> <span class="st">"Abhinav"</span>}</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="op">></span> user</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>(<span class="dt">User</span> { <span class="fu">id</span><span class="op">:</span> <span class="dv">1</span>, name<span class="op">:</span> <span class="st">"Abhinav"</span> })</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">import</span> <span class="dt">Data.Foreign.Generic</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="op">></span> userJSON <span class="ot">=</span> encodeJSON user</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="op">></span> userJSON</span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="st">"{\"name\":\"Abhinav\",\"id\":1}"</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">import</span> <span class="dt">Data.Foreign</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">import</span> <span class="dt">Control.Monad.Except.Trans</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">import</span> <span class="dt">Data.Identity</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a><span class="op">></span> dUser <span class="ot">=</span> decodeJSON<span class="ot"> userJSON ::</span> <span class="dt">F</span> <span class="dt">User</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a><span class="op">></span> eUser <span class="ot">=</span> <span class="kw">let</span> (<span class="dt">Identity</span> eUser) <span class="ot">=</span> runExceptT <span class="op">$</span> dUser <span class="kw">in</span> eUser</span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a><span class="op">></span> eUser</span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a>(<span class="dt">Right</span> (<span class="dt">User</span> { <span class="fu">id</span><span class="op">:</span> <span class="dv">1</span>, name<span class="op">:</span> <span class="st">"Abhinav"</span> }))</span></code></pre></div>
<p>We use <code>encodeJSON</code> and <code>decodeJSON</code> functions from the <a href="https://pursuit.purescript.org/packages/purescript-foreign-generic/4.3.0/docs/Data.Foreign.Generic" target="_blank" rel="noopener"><code>Data.Foreign.Generic</code></a> module to encode and decode the <code>User</code> instance to JSON. The return type of <code>decodeJSON</code> is a bit complicated as it needs to return the parsing errors too. In this case, the decoding returns no errors and we get back a <code>Right</code> with the correctly parsed <code>User</code> instance.</p>
<h2 data-track-content data-content-name="persisting-it" data-content-piece="ps-simple-rest-service" id="persisting-it">Persisting It</h2>
<p>Next, we add the support for saving a <code>User</code> instance to a Postgres database. First, we install the required libraries using bower and npm: <a href="https://github.com/brianc/node-postgres" target="_blank" rel="noopener"><code>pg</code></a> for Javascript bindings to call Postgres, <a href="https://pursuit.purescript.org/packages/purescript-aff" target="_blank" rel="noopener"><code>purescript-aff</code></a> for asynchronous processing and <a href="https://pursuit.purescript.org/packages/purescript-postgresql-client" target="_blank" rel="noopener"><code>purescript-postgresql-client</code></a> for PureScript wrapper over <code>pg</code>:</p>
<pre class="plain"><code>$ npm init -y
$ npm install pg@6.4.0 --save
$ bower install purescript-aff --save
$ bower install purescript-postgresql-client --save</code></pre>
<p>Before writing the code, we create the database and the <code>users</code> table using the command-line Postgres client:</p>
<pre class="plain"><code>$ psql postgres
psql (9.5.4)
Type "help" for help.
postgres=# create database simple_service;
CREATE DATABASE
postgres=# \c simple_service
You are now connected to database "simple_service" as user "abhinav".
simple_service=# create table users (id int primary key, name varchar(100) not null);
CREATE TABLE
simple_service=# \d users
Table "public.users"
Column | Type | Modifiers
--------+------------------------+-----------
id | integer | not null
name | character varying(100) | not null
Indexes:
"users_pkey" PRIMARY KEY, btree (id)</code></pre>
<p>Now we add support for converting a <code>User</code> instance to-and-from an SQL row by adding the following code in the <code>src/SimpleService/Types.purs</code> file:</p>
<div class="sourceCode" id="cb8" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Array</span> <span class="kw">as</span> <span class="dt">Array</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Either</span> (<span class="dt">Either</span>(..))</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Database.PostgreSQL</span> (class <span class="dt">FromSQLRow</span>, class <span class="dt">ToSQLRow</span>, fromSQLValue, toSQLValue)</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- code written earlier</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span><span class="ot"> userFromSQLRow ::</span> <span class="dt">FromSQLRow</span> <span class="dt">User</span> <span class="kw">where</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a> fromSQLRow [<span class="fu">id</span>, name] <span class="ot">=</span></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">User</span> <span class="op"><$></span> ({ <span class="fu">id</span><span class="op">:</span> _, name<span class="op">:</span> _} <span class="op"><$></span> fromSQLValue <span class="fu">id</span> <span class="op"><*></span> fromSQLValue name)</span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a> fromSQLRow xs <span class="ot">=</span> <span class="dt">Left</span> <span class="op">$</span> <span class="st">"Row has "</span> <span class="op"><></span> <span class="fu">show</span> n <span class="op"><></span> <span class="st">" fields, expecting 2."</span></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span> n <span class="ot">=</span> Array.length xs</span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span><span class="ot"> userToSQLRow ::</span> <span class="dt">ToSQLRow</span> <span class="dt">User</span> <span class="kw">where</span></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a> toSQLRow (<span class="dt">User</span> {<span class="fu">id</span>, name}) <span class="ot">=</span> [toSQLValue <span class="fu">id</span>, toSQLValue name]</span></code></pre></div>
<p>We can try out the persistence support in the REPL:</p>
<div class="sourceCode" id="cb9" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="op">$</span> pulp repl</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="dt">PSCi</span>, version <span class="fl">0.11</span><span class="op">.</span><span class="dv">6</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="dt">Type</span> <span class="op">:?</span> for help</span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a><span class="op">></span></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">import</span> <span class="dt">SimpleService.Types</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">import</span> <span class="dt">Control.Monad.Aff</span> (launchAff, liftEff')</span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">import</span> <span class="dt">Database.PostgreSQL</span> <span class="kw">as</span> <span class="dt">PG</span></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="op">></span> user <span class="ot">=</span> <span class="dt">User</span> { <span class="fu">id</span><span class="op">:</span> <span class="dv">1</span>, name<span class="op">:</span> <span class="st">"Abhinav"</span> }</span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a><span class="op">></span> databaseConfig <span class="ot">=</span> {user<span class="op">:</span> <span class="st">"abhinav"</span>, password<span class="op">:</span> <span class="st">""</span>, host<span class="op">:</span> <span class="st">"localhost"</span>, port<span class="op">:</span> <span class="dv">5432</span>, database<span class="op">:</span> <span class="st">"simple_service"</span>, <span class="fu">max</span><span class="op">:</span> <span class="dv">10</span>, idleTimeoutMillis<span class="op">:</span> <span class="dv">1000</span>}</span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="op">:</span>paste</span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a>… void <span class="op">$</span> launchAff <span class="kw">do</span></span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a>… pool <span class="ot"><-</span> PG.newPool databaseConfig</span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a>… PG.withConnection pool <span class="op">$</span> \conn <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a>… PG.execute conn (<span class="dt">PG.Query</span> <span class="st">"insert into users (id, name) values ($1, $2)"</span>) user</span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a>…</span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a>unit</span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">import</span> <span class="dt">Data.Foldable</span> (for_)</span>
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="kw">import</span> <span class="dt">Control.Monad.Eff.Console</span> (logShow)</span>
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a><span class="op">></span> <span class="op">:</span>paste</span>
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a>… void <span class="op">$</span> launchAff <span class="kw">do</span></span>
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a>… pool <span class="ot"><-</span> PG.newPool databaseConfig</span>
<span id="cb9-26"><a href="#cb9-26" aria-hidden="true" tabindex="-1"></a>… PG.withConnection pool <span class="op">$</span> \conn <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb9-27"><a href="#cb9-27" aria-hidden="true" tabindex="-1"></a>…<span class="ot"> users ::</span> <span class="dt">Array</span> <span class="dt">User</span> <span class="ot"><-</span> PG.query conn (<span class="dt">PG.Query</span> <span class="st">"select id, name from users where id = $1"</span>) (<span class="dt">PG.Row1</span> <span class="dv">1</span>)</span>
<span id="cb9-28"><a href="#cb9-28" aria-hidden="true" tabindex="-1"></a>… liftEff' <span class="op">$</span> void <span class="op">$</span> for_ users logShow</span>
<span id="cb9-29"><a href="#cb9-29" aria-hidden="true" tabindex="-1"></a>…</span>
<span id="cb9-30"><a href="#cb9-30" aria-hidden="true" tabindex="-1"></a>unit</span>
<span id="cb9-31"><a href="#cb9-31" aria-hidden="true" tabindex="-1"></a>(<span class="dt">User</span> { <span class="fu">id</span><span class="op">:</span> <span class="dv">1</span>, name<span class="op">:</span> <span class="st">"Abhinav"</span> })</span></code></pre></div>
<p>We create the <code>databaseConfig</code> record with the configs needed to connect to the database. Using the record, we create a new Postgres connection pool (<code>PG.newPool</code>) and get a connection from it (<code>PG.withConnection</code>). We call <code>PG.execute</code> with the connection, the SQL insert query for the users table and the <code>User</code> instance, to insert the user into the table. All of this is done inside <a href="https://pursuit.purescript.org/packages/purescript-aff/3.1.0/docs/Control.Monad.Aff#v:launchAff" target="_blank" rel="noopener"><code>launchAff</code></a> which takes care of sequencing the callbacks correctly to make the asynchronous code look synchronous.</p>
<p>Similarly, in the second part, we query the table using <code>PG.query</code> function by calling it with a connection, the SQL select query and the <code>User</code> ID as the query parameter. It returns an <code>Array</code> of users which we log to the console using the <code>logShow</code> function.</p>
<p>We use this experiment to write the persistence related code in the <code>src/SimpleService/Persistence.purs</code> file:</p>
<div class="sourceCode" id="cb10" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">SimpleService.Persistence</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a> ( insertUser</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a> , findUser</span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a> , updateUser</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a> , deleteUser</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a> , listUsers</span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a> ) <span class="kw">where</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Aff</span> (<span class="dt">Aff</span>)</span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Array</span> <span class="kw">as</span> <span class="dt">Array</span></span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Maybe</span> (<span class="dt">Maybe</span>)</span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Database.PostgreSQL</span> <span class="kw">as</span> <span class="dt">PG</span></span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Types</span> (<span class="dt">User</span>(..), <span class="dt">UserID</span>)</span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a><span class="ot">insertUserQuery ::</span> <span class="dt">String</span></span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a>insertUserQuery <span class="ot">=</span> <span class="st">"insert into users (id, name) values ($1, $2)"</span></span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true" tabindex="-1"></a><span class="ot">findUserQuery ::</span> <span class="dt">String</span></span>
<span id="cb10-21"><a href="#cb10-21" aria-hidden="true" tabindex="-1"></a>findUserQuery <span class="ot">=</span> <span class="st">"select id, name from users where id = $1"</span></span>
<span id="cb10-22"><a href="#cb10-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-23"><a href="#cb10-23" aria-hidden="true" tabindex="-1"></a><span class="ot">updateUserQuery ::</span> <span class="dt">String</span></span>
<span id="cb10-24"><a href="#cb10-24" aria-hidden="true" tabindex="-1"></a>updateUserQuery <span class="ot">=</span> <span class="st">"update users set name = $1 where id = $2"</span></span>
<span id="cb10-25"><a href="#cb10-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-26"><a href="#cb10-26" aria-hidden="true" tabindex="-1"></a><span class="ot">deleteUserQuery ::</span> <span class="dt">String</span></span>
<span id="cb10-27"><a href="#cb10-27" aria-hidden="true" tabindex="-1"></a>deleteUserQuery <span class="ot">=</span> <span class="st">"delete from users where id = $1"</span></span>
<span id="cb10-28"><a href="#cb10-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-29"><a href="#cb10-29" aria-hidden="true" tabindex="-1"></a><span class="ot">listUsersQuery ::</span> <span class="dt">String</span></span>
<span id="cb10-30"><a href="#cb10-30" aria-hidden="true" tabindex="-1"></a>listUsersQuery <span class="ot">=</span> <span class="st">"select id, name from users"</span></span>
<span id="cb10-31"><a href="#cb10-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-32"><a href="#cb10-32" aria-hidden="true" tabindex="-1"></a><span class="ot">insertUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Connection</span> <span class="ot">-></span> <span class="dt">User</span></span>
<span id="cb10-33"><a href="#cb10-33" aria-hidden="true" tabindex="-1"></a> <span class="ot">-></span> <span class="dt">Aff</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff) <span class="dt">Unit</span></span>
<span id="cb10-34"><a href="#cb10-34" aria-hidden="true" tabindex="-1"></a>insertUser conn user <span class="ot">=</span></span>
<span id="cb10-35"><a href="#cb10-35" aria-hidden="true" tabindex="-1"></a> PG.execute conn (<span class="dt">PG.Query</span> insertUserQuery) user</span>
<span id="cb10-36"><a href="#cb10-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-37"><a href="#cb10-37" aria-hidden="true" tabindex="-1"></a><span class="ot">findUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Connection</span> <span class="ot">-></span> <span class="dt">UserID</span></span>
<span id="cb10-38"><a href="#cb10-38" aria-hidden="true" tabindex="-1"></a> <span class="ot">-></span> <span class="dt">Aff</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff) (<span class="dt">Maybe</span> <span class="dt">User</span>)</span>
<span id="cb10-39"><a href="#cb10-39" aria-hidden="true" tabindex="-1"></a>findUser conn userID <span class="ot">=</span></span>
<span id="cb10-40"><a href="#cb10-40" aria-hidden="true" tabindex="-1"></a> <span class="fu">map</span> Array.head <span class="op">$</span> PG.query conn (<span class="dt">PG.Query</span> findUserQuery) (<span class="dt">PG.Row1</span> userID)</span>
<span id="cb10-41"><a href="#cb10-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-42"><a href="#cb10-42" aria-hidden="true" tabindex="-1"></a><span class="ot">updateUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Connection</span> <span class="ot">-></span> <span class="dt">User</span></span>
<span id="cb10-43"><a href="#cb10-43" aria-hidden="true" tabindex="-1"></a> <span class="ot">-></span> <span class="dt">Aff</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff) <span class="dt">Unit</span></span>
<span id="cb10-44"><a href="#cb10-44" aria-hidden="true" tabindex="-1"></a>updateUser conn (<span class="dt">User</span> {<span class="fu">id</span>, name}) <span class="ot">=</span></span>
<span id="cb10-45"><a href="#cb10-45" aria-hidden="true" tabindex="-1"></a> PG.execute conn (<span class="dt">PG.Query</span> updateUserQuery) (<span class="dt">PG.Row2</span> name <span class="fu">id</span>)</span>
<span id="cb10-46"><a href="#cb10-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-47"><a href="#cb10-47" aria-hidden="true" tabindex="-1"></a><span class="ot">deleteUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Connection</span> <span class="ot">-></span> <span class="dt">UserID</span></span>
<span id="cb10-48"><a href="#cb10-48" aria-hidden="true" tabindex="-1"></a> <span class="ot">-></span> <span class="dt">Aff</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff) <span class="dt">Unit</span></span>
<span id="cb10-49"><a href="#cb10-49" aria-hidden="true" tabindex="-1"></a>deleteUser conn userID <span class="ot">=</span></span>
<span id="cb10-50"><a href="#cb10-50" aria-hidden="true" tabindex="-1"></a> PG.execute conn (<span class="dt">PG.Query</span> deleteUserQuery) (<span class="dt">PG.Row1</span> userID)</span>
<span id="cb10-51"><a href="#cb10-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-52"><a href="#cb10-52" aria-hidden="true" tabindex="-1"></a><span class="ot">listUsers ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Connection</span></span>
<span id="cb10-53"><a href="#cb10-53" aria-hidden="true" tabindex="-1"></a> <span class="ot">-></span> <span class="dt">Aff</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff) (<span class="dt">Array</span> <span class="dt">User</span>)</span>
<span id="cb10-54"><a href="#cb10-54" aria-hidden="true" tabindex="-1"></a>listUsers conn <span class="ot">=</span></span>
<span id="cb10-55"><a href="#cb10-55" aria-hidden="true" tabindex="-1"></a> PG.query conn (<span class="dt">PG.Query</span> listUsersQuery) <span class="dt">PG.Row0</span></span></code></pre></div>
<h2 data-track-content data-content-name="serving-it" data-content-piece="ps-simple-rest-service" id="serving-it">Serving It</h2>
<p>We can now write a simple HTTP API over the persistence layer using <a href="https://expressjs.com" target="_blank" rel="noopener">Express</a> to provide CRUD functionality for users. Let’s install Express and <a href="https://pursuit.purescript.org/packages/purescript-express" target="_blank" rel="noopener">purescript-express</a>, the PureScript wrapper over it:</p>
<pre class="plain"><code>$ npm install express --save
$ bower install purescript-express --save</code></pre>
<h3 id="getting-a-user">Getting a User</h3>
<p>We do this top-down. First, we change <code>src/Main.purs</code> to run the HTTP server by providing the server port and database configuration:</p>
<div class="sourceCode" id="cb12" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">Main</span> <span class="kw">where</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff</span> (<span class="dt">Eff</span>)</span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff.Console</span> (<span class="dt">CONSOLE</span>)</span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Database.PostgreSQL</span> <span class="kw">as</span> <span class="dt">PG</span></span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Types</span> (<span class="dt">EXPRESS</span>)</span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Server</span> (runServer)</span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a><span class="ot">main ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">Eff</span> (<span class="ot"> console ::</span> <span class="dt">CONSOLE</span></span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> express ::</span> <span class="dt">EXPRESS</span></span>
<span id="cb12-13"><a href="#cb12-13" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span></span>
<span id="cb12-14"><a href="#cb12-14" aria-hidden="true" tabindex="-1"></a> <span class="op">|</span> eff) <span class="dt">Unit</span></span>
<span id="cb12-15"><a href="#cb12-15" aria-hidden="true" tabindex="-1"></a>main <span class="ot">=</span> runServer port databaseConfig</span>
<span id="cb12-16"><a href="#cb12-16" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb12-17"><a href="#cb12-17" aria-hidden="true" tabindex="-1"></a> port <span class="ot">=</span> <span class="dv">4000</span></span>
<span id="cb12-18"><a href="#cb12-18" aria-hidden="true" tabindex="-1"></a> databaseConfig <span class="ot">=</span> { user<span class="op">:</span> <span class="st">"abhinav"</span></span>
<span id="cb12-19"><a href="#cb12-19" aria-hidden="true" tabindex="-1"></a> , password<span class="op">:</span> <span class="st">""</span></span>
<span id="cb12-20"><a href="#cb12-20" aria-hidden="true" tabindex="-1"></a> , host<span class="op">:</span> <span class="st">"localhost"</span></span>
<span id="cb12-21"><a href="#cb12-21" aria-hidden="true" tabindex="-1"></a> , port<span class="op">:</span> <span class="dv">5432</span></span>
<span id="cb12-22"><a href="#cb12-22" aria-hidden="true" tabindex="-1"></a> , database<span class="op">:</span> <span class="st">"simple_service"</span></span>
<span id="cb12-23"><a href="#cb12-23" aria-hidden="true" tabindex="-1"></a> , <span class="fu">max</span><span class="op">:</span> <span class="dv">10</span></span>
<span id="cb12-24"><a href="#cb12-24" aria-hidden="true" tabindex="-1"></a> , idleTimeoutMillis<span class="op">:</span> <span class="dv">1000</span></span>
<span id="cb12-25"><a href="#cb12-25" aria-hidden="true" tabindex="-1"></a> }</span></code></pre></div>
<p>Next, we wire up the server routes to the handlers in <code>src/SimpleService/Server.purs</code>:</p>
<div class="sourceCode" id="cb13" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">SimpleService.Server</span> (runServer) <span class="kw">where</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Aff</span> (runAff)</span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff</span> (<span class="dt">Eff</span>)</span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff.Class</span> (liftEff)</span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Eff.Console</span> (<span class="dt">CONSOLE</span>, log, logShow)</span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Database.PostgreSQL</span> <span class="kw">as</span> <span class="dt">PG</span></span>
<span id="cb13-10"><a href="#cb13-10" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.App</span> (<span class="dt">App</span>, get, listenHttp)</span>
<span id="cb13-11"><a href="#cb13-11" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Types</span> (<span class="dt">EXPRESS</span>)</span>
<span id="cb13-12"><a href="#cb13-12" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Handler</span> (getUser)</span>
<span id="cb13-13"><a href="#cb13-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-14"><a href="#cb13-14" aria-hidden="true" tabindex="-1"></a><span class="ot">app ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">App</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb13-15"><a href="#cb13-15" aria-hidden="true" tabindex="-1"></a>app pool <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb13-16"><a href="#cb13-16" aria-hidden="true" tabindex="-1"></a> get <span class="st">"/v1/user/:id"</span> <span class="op">$</span> getUser pool</span>
<span id="cb13-17"><a href="#cb13-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-18"><a href="#cb13-18" aria-hidden="true" tabindex="-1"></a><span class="ot">runServer ::</span> <span class="kw">forall</span> eff<span class="op">.</span></span>
<span id="cb13-19"><a href="#cb13-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">Int</span></span>
<span id="cb13-20"><a href="#cb13-20" aria-hidden="true" tabindex="-1"></a> <span class="ot">-></span> <span class="dt">PG.PoolConfiguration</span></span>
<span id="cb13-21"><a href="#cb13-21" aria-hidden="true" tabindex="-1"></a> <span class="ot">-></span> <span class="dt">Eff</span> (<span class="ot"> express ::</span> <span class="dt">EXPRESS</span></span>
<span id="cb13-22"><a href="#cb13-22" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span></span>
<span id="cb13-23"><a href="#cb13-23" aria-hidden="true" tabindex="-1"></a> ,<span class="ot"> console ::</span> <span class="dt">CONSOLE</span></span>
<span id="cb13-24"><a href="#cb13-24" aria-hidden="true" tabindex="-1"></a> <span class="op">|</span> eff ) <span class="dt">Unit</span></span>
<span id="cb13-25"><a href="#cb13-25" aria-hidden="true" tabindex="-1"></a>runServer port databaseConfig <span class="ot">=</span> void <span class="op">$</span> runAff logShow <span class="fu">pure</span> <span class="kw">do</span></span>
<span id="cb13-26"><a href="#cb13-26" aria-hidden="true" tabindex="-1"></a> pool <span class="ot"><-</span> PG.newPool databaseConfig</span>
<span id="cb13-27"><a href="#cb13-27" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> app' <span class="ot">=</span> app pool</span>
<span id="cb13-28"><a href="#cb13-28" aria-hidden="true" tabindex="-1"></a> void <span class="op">$</span> liftEff <span class="op">$</span> listenHttp app' port \_ <span class="ot">-></span> <span class="fu">log</span> <span class="op">$</span> <span class="st">"Server listening on :"</span> <span class="op"><></span> <span class="fu">show</span> port</span></code></pre></div>
<p><code>runServer</code> creates a PostgreSQL connection pool and passes it to the <code>app</code> function which creates the Express application, which in turn, binds it to the handler <code>getUser</code>. Then it launches the HTTP server by calling <code>listenHttp</code>.</p>
<p>Finally, we write the actual <code>getUser</code> handler in <code>src/SimpleService/Handler.purs</code>:</p>
<div class="sourceCode" id="cb14" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">SimpleService.Handler</span> <span class="kw">where</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Control.Monad.Aff.Class</span> (liftAff)</span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foreign.Class</span> (encode)</span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Int</span> (fromString)</span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Maybe</span> (<span class="dt">Maybe</span>(..))</span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Database.PostgreSQL</span> <span class="kw">as</span> <span class="dt">PG</span></span>
<span id="cb14-10"><a href="#cb14-10" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Handler</span> (<span class="dt">Handler</span>)</span>
<span id="cb14-11"><a href="#cb14-11" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Request</span> (getRouteParam)</span>
<span id="cb14-12"><a href="#cb14-12" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Response</span> (end, sendJson, setStatus)</span>
<span id="cb14-13"><a href="#cb14-13" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Persistence</span> <span class="kw">as</span> <span class="dt">P</span></span>
<span id="cb14-14"><a href="#cb14-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-15"><a href="#cb14-15" aria-hidden="true" tabindex="-1"></a><span class="ot">getUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb14-16"><a href="#cb14-16" aria-hidden="true" tabindex="-1"></a>getUser pool <span class="ot">=</span> getRouteParam <span class="st">"id"</span> <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb14-17"><a href="#cb14-17" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User ID is required"</span> }</span>
<span id="cb14-18"><a href="#cb14-18" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> sUserId <span class="ot">-></span> <span class="kw">case</span> fromString sUserId <span class="kw">of</span></span>
<span id="cb14-19"><a href="#cb14-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User ID must be an integer: "</span> <span class="op"><></span> sUserId }</span>
<span id="cb14-20"><a href="#cb14-20" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> userId <span class="ot">-></span> liftAff (PG.withConnection pool <span class="op">$</span> <span class="fu">flip</span> P.findUser userId) <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb14-21"><a href="#cb14-21" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">404</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User not found with id: "</span> <span class="op"><></span> sUserId }</span>
<span id="cb14-22"><a href="#cb14-22" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> user <span class="ot">-></span> respond <span class="dv">200</span> (encode user)</span>
<span id="cb14-23"><a href="#cb14-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-24"><a href="#cb14-24" aria-hidden="true" tabindex="-1"></a><span class="ot">respond ::</span> <span class="kw">forall</span> eff a<span class="op">.</span> <span class="dt">Int</span> <span class="ot">-></span> a <span class="ot">-></span> <span class="dt">Handler</span> eff</span>
<span id="cb14-25"><a href="#cb14-25" aria-hidden="true" tabindex="-1"></a>respond status body <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb14-26"><a href="#cb14-26" aria-hidden="true" tabindex="-1"></a> setStatus status</span>
<span id="cb14-27"><a href="#cb14-27" aria-hidden="true" tabindex="-1"></a> sendJson body</span>
<span id="cb14-28"><a href="#cb14-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-29"><a href="#cb14-29" aria-hidden="true" tabindex="-1"></a><span class="ot">respondNoContent ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">Int</span> <span class="ot">-></span> <span class="dt">Handler</span> eff</span>
<span id="cb14-30"><a href="#cb14-30" aria-hidden="true" tabindex="-1"></a>respondNoContent status <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb14-31"><a href="#cb14-31" aria-hidden="true" tabindex="-1"></a> setStatus status</span>
<span id="cb14-32"><a href="#cb14-32" aria-hidden="true" tabindex="-1"></a> end</span></code></pre></div>
<p><code>getUser</code> validates the route parameter for valid user ID, sending error HTTP responses in case of failures. It then calls <code>findUser</code> to find the user and returns appropriate response.</p>
<p>We can test this on the command-line using <a href="https://httpie.org" target="_blank" rel="noopener">HTTPie</a>. We run <code>pulp --watch run</code> in one terminal to start the server with file watching, and test it from another terminal:</p>
<pre class="plain"><code>$ pulp --watch run
* Building project in ps-simple-rest-service
* Build successful.
Server listening on :4000</code></pre>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/user/1 # should return the user we created earlier
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 25
Content-Type: application/json; charset=utf-8
Date: Sun, 10 Sep 2017 14:32:52 GMT
ETag: W/"19-qmtK9XY+WDrqHTgqtFlV+h+NGOY"
X-Powered-By: Express
{
"id": 1,
"name": "Abhinav"
}</code></pre>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/user/s
HTTP/1.1 422 Unprocessable Entity
Connection: keep-alive
Content-Length: 38
Content-Type: application/json; charset=utf-8
Date: Sun, 10 Sep 2017 14:36:04 GMT
ETag: W/"26-//tvORl1gGDUMwgSaqbEpJhuadI"
X-Powered-By: Express
{
"error": "User ID must be an integer: s"
}</code></pre>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/user/2
HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Length: 36
Content-Type: application/json; charset=utf-8
Date: Sun, 10 Sep 2017 14:36:11 GMT
ETag: W/"24-IyD5VT4E8/m3kvpwycRBQunI7Uc"
X-Powered-By: Express
{
"error": "User not found with id: 2"
}</code></pre>
<h3 id="deleting-a-user">Deleting a User</h3>
<p><code>deleteUser</code> handler is similar. We add the route in the <code>app</code> function in the <code>src/SimpleService/Server.purs</code> file:</p>
<div class="sourceCode" id="cb19" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.App</span> (<span class="dt">App</span>, delete, get, listenHttp)</span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Handler</span> (deleteUser, getUser)</span>
<span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb19-5"><a href="#cb19-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb19-6"><a href="#cb19-6" aria-hidden="true" tabindex="-1"></a><span class="ot">app ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">App</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb19-7"><a href="#cb19-7" aria-hidden="true" tabindex="-1"></a>app pool <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb19-8"><a href="#cb19-8" aria-hidden="true" tabindex="-1"></a> get <span class="st">"/v1/user/:id"</span> <span class="op">$</span> getUser pool</span>
<span id="cb19-9"><a href="#cb19-9" aria-hidden="true" tabindex="-1"></a> delete <span class="st">"/v1/user/:id"</span> <span class="op">$</span> deleteUser pool</span>
<span id="cb19-10"><a href="#cb19-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb19-11"><a href="#cb19-11" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span></code></pre></div>
<p>And we add the handler in the <code>src/SimpleService/Handler.purs</code> file:</p>
<div class="sourceCode" id="cb20" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="ot">deleteUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a>deleteUser pool <span class="ot">=</span> getRouteParam <span class="st">"id"</span> <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User ID is required"</span> }</span>
<span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> sUserId <span class="ot">-></span> <span class="kw">case</span> fromString sUserId <span class="kw">of</span></span>
<span id="cb20-5"><a href="#cb20-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User ID must be an integer: "</span> <span class="op"><></span> sUserId }</span>
<span id="cb20-6"><a href="#cb20-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> userId <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb20-7"><a href="#cb20-7" aria-hidden="true" tabindex="-1"></a> found <span class="ot"><-</span> liftAff <span class="op">$</span> PG.withConnection pool \conn <span class="ot">-></span> PG.withTransaction conn <span class="kw">do</span></span>
<span id="cb20-8"><a href="#cb20-8" aria-hidden="true" tabindex="-1"></a> P.findUser conn userId <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb20-9"><a href="#cb20-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">pure</span> false</span>
<span id="cb20-10"><a href="#cb20-10" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> _ <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb20-11"><a href="#cb20-11" aria-hidden="true" tabindex="-1"></a> P.deleteUser conn userId</span>
<span id="cb20-12"><a href="#cb20-12" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> true</span>
<span id="cb20-13"><a href="#cb20-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">if</span> found</span>
<span id="cb20-14"><a href="#cb20-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">then</span> respondNoContent <span class="dv">204</span></span>
<span id="cb20-15"><a href="#cb20-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">else</span> respond <span class="dv">404</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User not found with id: "</span> <span class="op"><></span> sUserId }</span></code></pre></div>
<p>After the usual validations on the route param, <code>deleteUser</code> tries to find the user by the given user ID and if found, it deletes the user. Both the persistence related functions are run inside a single SQL transaction using <code>PG.withTransaction</code> function. <code>deleteUser</code> return 404 status if the user is not found, else it returns 204 status.</p>
<p>Let’s try it out:</p>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/user/1
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 25
Content-Type: application/json; charset=utf-8
Date: Mon, 11 Sep 2017 05:10:50 GMT
ETag: W/"19-GC9FAtbd81t7CtrQgsNuc8HITXU"
X-Powered-By: Express
{
"id": 1,
"name": "Abhinav"
}</code></pre>
<pre class="plain"><code>$ http DELETE http://localhost:4000/v1/user/1
HTTP/1.1 204 No Content
Connection: keep-alive
Date: Mon, 11 Sep 2017 05:10:56 GMT
X-Powered-By: Express</code></pre>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/user/1
HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Length: 37
Content-Type: application/json; charset=utf-8
Date: Mon, 11 Sep 2017 05:11:03 GMT
ETag: W/"25-Eoc4ZbEF73CyW8EGh6t2jqI8mLU"
X-Powered-By: Express
{
"error": "User not found with id: 1"
}</code></pre>
<pre class="plain"><code>$ http DELETE http://localhost:4000/v1/user/1
HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Length: 37
Content-Type: application/json; charset=utf-8
Date: Mon, 11 Sep 2017 05:11:05 GMT
ETag: W/"25-Eoc4ZbEF73CyW8EGh6t2jqI8mLU"
X-Powered-By: Express
{
"error": "User not found with id: 1"
}</code></pre>
<div class="page-break">
</div>
<h3 id="creating-a-user">Creating a User</h3>
<p><code>createUser</code> handler is a bit more involved. First, we add an Express middleware to parse the body of the request as JSON. We use <a href="https://github.com/expressjs/body-parser" target="_blank" rel="noopener"><code>body-parser</code></a> for this and access it through PureScript <a href="https://github.com/purescript/documentation/blob/master/guides/FFI.md" target="_blank" rel="noopener">FFI</a>. We create a new file <code>src/SimpleService/Middleware/BodyParser.js</code> with the content:</p>
<div class="sourceCode" id="cb25" data-lang="javascript"><pre class="sourceCode numberSource javascript"><code class="sourceCode javascript"><span id="cb25-1"><a href="#cb25-1" aria-hidden="true" tabindex="-1"></a><span class="st">"use strict"</span><span class="op">;</span></span>
<span id="cb25-2"><a href="#cb25-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb25-3"><a href="#cb25-3" aria-hidden="true" tabindex="-1"></a><span class="kw">var</span> bodyParser <span class="op">=</span> <span class="pp">require</span>(<span class="st">"body-parser"</span>)<span class="op">;</span></span>
<span id="cb25-4"><a href="#cb25-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb25-5"><a href="#cb25-5" aria-hidden="true" tabindex="-1"></a>exports<span class="op">.</span><span class="at">jsonBodyParser</span> <span class="op">=</span> bodyParser<span class="op">.</span><span class="fu">json</span>({</span>
<span id="cb25-6"><a href="#cb25-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">limit</span><span class="op">:</span> <span class="st">"5mb"</span></span>
<span id="cb25-7"><a href="#cb25-7" aria-hidden="true" tabindex="-1"></a>})<span class="op">;</span></span></code></pre></div>
<p>And write a wrapper for it in the file <code>src/SimpleService/Middleware/BodyParser.purs</code> with the content:</p>
<div class="sourceCode" id="cb26" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb26-1"><a href="#cb26-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">SimpleService.Middleware.BodyParser</span> <span class="kw">where</span></span>
<span id="cb26-2"><a href="#cb26-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-3"><a href="#cb26-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Prelude</span></span>
<span id="cb26-4"><a href="#cb26-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Function.Uncurried</span> (<span class="dt">Fn3</span>)</span>
<span id="cb26-5"><a href="#cb26-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Types</span> (<span class="dt">ExpressM</span>, <span class="dt">Response</span>, <span class="dt">Request</span>)</span>
<span id="cb26-6"><a href="#cb26-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb26-7"><a href="#cb26-7" aria-hidden="true" tabindex="-1"></a>foreign <span class="kw">import</span> jsonBodyParser ::</span>
<span id="cb26-8"><a href="#cb26-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">forall</span> e<span class="op">.</span> <span class="dt">Fn3</span> <span class="dt">Request</span> <span class="dt">Response</span> (<span class="dt">ExpressM</span> e <span class="dt">Unit</span>) (<span class="dt">ExpressM</span> e <span class="dt">Unit</span>)</span></code></pre></div>
<p>We also install the <code>body-parser</code> npm dependency:</p>
<pre class="plain"><code>$ npm install --save body-parser</code></pre>
<p>Next, we change the <code>app</code> function in the <code>src/SimpleService/Server.purs</code> file to add the middleware and the route:</p>
<div class="sourceCode" id="cb28" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb28-1"><a href="#cb28-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb28-2"><a href="#cb28-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.App</span> (<span class="dt">App</span>, delete, get, listenHttp, post, useExternal)</span>
<span id="cb28-3"><a href="#cb28-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Handler</span> (createUser, deleteUser, getUser)</span>
<span id="cb28-4"><a href="#cb28-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Middleware.BodyParser</span> (jsonBodyParser)</span>
<span id="cb28-5"><a href="#cb28-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb28-6"><a href="#cb28-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb28-7"><a href="#cb28-7" aria-hidden="true" tabindex="-1"></a><span class="ot">app ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">App</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb28-8"><a href="#cb28-8" aria-hidden="true" tabindex="-1"></a>app pool <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb28-9"><a href="#cb28-9" aria-hidden="true" tabindex="-1"></a> useExternal jsonBodyParser</span>
<span id="cb28-10"><a href="#cb28-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb28-11"><a href="#cb28-11" aria-hidden="true" tabindex="-1"></a> get <span class="st">"/v1/user/:id"</span> <span class="op">$</span> getUser pool</span>
<span id="cb28-12"><a href="#cb28-12" aria-hidden="true" tabindex="-1"></a> delete <span class="st">"/v1/user/:id"</span> <span class="op">$</span> deleteUser pool</span>
<span id="cb28-13"><a href="#cb28-13" aria-hidden="true" tabindex="-1"></a> post <span class="st">"/v1/users"</span> <span class="op">$</span> createUser pool</span></code></pre></div>
<p>And finally, we write the handler in the <code>src/SimpleService/Handler.purs</code> file:</p>
<div class="sourceCode" id="cb29" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb29-1"><a href="#cb29-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb29-2"><a href="#cb29-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Either</span> (<span class="dt">Either</span>(..))</span>
<span id="cb29-3"><a href="#cb29-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foldable</span> (intercalate)</span>
<span id="cb29-4"><a href="#cb29-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foreign</span> (renderForeignError)</span>
<span id="cb29-5"><a href="#cb29-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Request</span> (getBody, getRouteParam)</span>
<span id="cb29-6"><a href="#cb29-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Types</span></span>
<span id="cb29-7"><a href="#cb29-7" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb29-8"><a href="#cb29-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb29-9"><a href="#cb29-9" aria-hidden="true" tabindex="-1"></a><span class="ot">createUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb29-10"><a href="#cb29-10" aria-hidden="true" tabindex="-1"></a>createUser pool <span class="ot">=</span> getBody <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb29-11"><a href="#cb29-11" aria-hidden="true" tabindex="-1"></a> <span class="dt">Left</span> errs <span class="ot">-></span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> intercalate <span class="st">", "</span> <span class="op">$</span> <span class="fu">map</span> renderForeignError errs}</span>
<span id="cb29-12"><a href="#cb29-12" aria-hidden="true" tabindex="-1"></a> <span class="dt">Right</span> u<span class="op">@</span>(<span class="dt">User</span> user) <span class="ot">-></span></span>
<span id="cb29-13"><a href="#cb29-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">if</span> user<span class="op">.</span><span class="fu">id</span> <span class="op"><=</span> <span class="dv">0</span></span>
<span id="cb29-14"><a href="#cb29-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">then</span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User ID must be positive: "</span> <span class="op"><></span> <span class="fu">show</span> user<span class="op">.</span><span class="fu">id</span>}</span>
<span id="cb29-15"><a href="#cb29-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">else</span> <span class="kw">if</span> user<span class="op">.</span>name <span class="op">==</span> <span class="st">""</span></span>
<span id="cb29-16"><a href="#cb29-16" aria-hidden="true" tabindex="-1"></a> <span class="kw">then</span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User name must not be empty"</span> }</span>
<span id="cb29-17"><a href="#cb29-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">else</span> <span class="kw">do</span></span>
<span id="cb29-18"><a href="#cb29-18" aria-hidden="true" tabindex="-1"></a> liftAff (PG.withConnection pool <span class="op">$</span> <span class="fu">flip</span> P.insertUser u)</span>
<span id="cb29-19"><a href="#cb29-19" aria-hidden="true" tabindex="-1"></a> respondNoContent <span class="dv">201</span></span></code></pre></div>
<p><code>createUser</code> calls <a href="https://pursuit.purescript.org/packages/purescript-express/0.5.2/docs/Node.Express.Request#v:getBody" target="_blank" rel="noopener"><code>getBody</code></a> which has type signature <code>forall e a. (Decode a) => HandlerM (express :: EXPRESS | e) (Either MultipleErrors a)</code>. It returns either a list of parsing errors or a parsed instance, which in our case is a <code>User</code>. In case of errors, we just return the errors rendered as string with a 422 status. If we get a parsed <code>User</code> instance, we do some validations on it, returning appropriate error messages. If all validations pass, we create the user in the database by calling <code>insertUser</code> from the persistence layer and respond with a status 201.</p>
<p>We can try it out:</p>
<pre class="plain"><code>$ http POST http://localhost:4000/v1/users name="abhinav"
HTTP/1.1 422 Unprocessable Entity
Connection: keep-alive
Content-Length: 97
Content-Type: application/json; charset=utf-8
Date: Mon, 11 Sep 2017 05:51:28 GMT
ETag: W/"61-BgsrMukZpImcdwAJEKCZ+70WBb8"
X-Powered-By: Express
{
"error": "Error at array index 0: (ErrorAtProperty \"id\" (TypeMismatch \"Int\" \"Undefined\"))"
}</code></pre>
<pre class="plain"><code>$ http POST http://localhost:4000/v1/users id:=1 name=""
HTTP/1.1 422 Unprocessable Entity
Connection: keep-alive
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 11 Sep 2017 05:51:42 GMT
ETag: W/"27-JQsh12xu/rEFdWy8REF4NMtBUB4"
X-Powered-By: Express
{
"error": "User name must not be empty"
}</code></pre>
<pre class="plain"><code>$ http POST http://localhost:4000/v1/users id:=1 name="abhinav"
HTTP/1.1 201 Created
Connection: keep-alive
Content-Length: 0
Date: Mon, 11 Sep 2017 05:52:23 GMT
X-Powered-By: Express</code></pre>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/user/1
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 25
Content-Type: application/json; charset=utf-8
Date: Mon, 11 Sep 2017 05:52:30 GMT
ETag: W/"19-GC9FAtbd81t7CtrQgsNuc8HITXU"
X-Powered-By: Express
{
"id": 1,
"name": "abhinav"
}</code></pre>
<p>First try returns a parsing failure because we didn’t provide the <code>id</code> field. Second try is a validation failure because the name was empty. Third try is a success which we confirm by doing a <code>GET</code> request next.</p>
<h3 id="updating-a-user">Updating a User</h3>
<p>We want to allow a user’s name to be updated through the API, but not the user’s ID. So we add a new type to <code>src/SimpleService/Types.purs</code> to represent a possible change in user’s name:</p>
<div class="sourceCode" id="cb34" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb34-1"><a href="#cb34-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb34-2"><a href="#cb34-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foreign.NullOrUndefined</span> (<span class="dt">NullOrUndefined</span>)</span>
<span id="cb34-3"><a href="#cb34-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb34-4"><a href="#cb34-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-5"><a href="#cb34-5" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">UserPatch</span> <span class="ot">=</span> <span class="dt">UserPatch</span> {<span class="ot"> name ::</span> <span class="dt">NullOrUndefined</span> <span class="dt">String</span> }</span>
<span id="cb34-6"><a href="#cb34-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-7"><a href="#cb34-7" aria-hidden="true" tabindex="-1"></a>derive <span class="kw">instance</span><span class="ot"> genericUserPatch ::</span> <span class="dt">Generic</span> <span class="dt">UserPatch</span> _</span>
<span id="cb34-8"><a href="#cb34-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-9"><a href="#cb34-9" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span><span class="ot"> decodeUserPatch ::</span> <span class="dt">Decode</span> <span class="dt">UserPatch</span> <span class="kw">where</span></span>
<span id="cb34-10"><a href="#cb34-10" aria-hidden="true" tabindex="-1"></a> decode <span class="ot">=</span> genericDecode <span class="op">$</span> defaultOptions { unwrapSingleConstructors <span class="ot">=</span> true }</span></code></pre></div>
<p><a href="https://pursuit.purescript.org/packages/purescript-foreign-generic/4.3.0/docs/Data.Foreign.NullOrUndefined#t:NullOrUndefined" target="_blank" rel="noopener"><code>NullOrUndefined</code></a> is a wrapper over <code>Maybe</code> with added support for Javascript <code>null</code> and <code>undefined</code> values. We define <code>UserPatch</code> as having a possibly null (or undefined) <code>name</code> field.</p>
<p>Now we can add the corresponding handler in <code>src/SimpleService/Handlers.purs</code>:</p>
<div class="sourceCode" id="cb35" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb35-1"><a href="#cb35-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb35-2"><a href="#cb35-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Data.Foreign.NullOrUndefined</span> (unNullOrUndefined)</span>
<span id="cb35-3"><a href="#cb35-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb35-4"><a href="#cb35-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb35-5"><a href="#cb35-5" aria-hidden="true" tabindex="-1"></a><span class="ot">updateUser ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb35-6"><a href="#cb35-6" aria-hidden="true" tabindex="-1"></a>updateUser pool <span class="ot">=</span> getRouteParam <span class="st">"id"</span> <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb35-7"><a href="#cb35-7" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User ID is required"</span> }</span>
<span id="cb35-8"><a href="#cb35-8" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> sUserId <span class="ot">-></span> <span class="kw">case</span> fromString sUserId <span class="kw">of</span></span>
<span id="cb35-9"><a href="#cb35-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User ID must be positive: "</span> <span class="op"><></span> sUserId }</span>
<span id="cb35-10"><a href="#cb35-10" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> userId <span class="ot">-></span> getBody <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb35-11"><a href="#cb35-11" aria-hidden="true" tabindex="-1"></a> <span class="dt">Left</span> errs <span class="ot">-></span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> intercalate <span class="st">", "</span> <span class="op">$</span> <span class="fu">map</span> renderForeignError errs}</span>
<span id="cb35-12"><a href="#cb35-12" aria-hidden="true" tabindex="-1"></a> <span class="dt">Right</span> (<span class="dt">UserPatch</span> userPatch) <span class="ot">-></span> <span class="kw">case</span> unNullOrUndefined userPatch<span class="op">.</span>name <span class="kw">of</span></span>
<span id="cb35-13"><a href="#cb35-13" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respondNoContent <span class="dv">204</span></span>
<span id="cb35-14"><a href="#cb35-14" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> userName <span class="ot">-></span> <span class="kw">if</span> userName <span class="op">==</span> <span class="st">""</span></span>
<span id="cb35-15"><a href="#cb35-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">then</span> respond <span class="dv">422</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User name must not be empty"</span> }</span>
<span id="cb35-16"><a href="#cb35-16" aria-hidden="true" tabindex="-1"></a> <span class="kw">else</span> <span class="kw">do</span></span>
<span id="cb35-17"><a href="#cb35-17" aria-hidden="true" tabindex="-1"></a> savedUser <span class="ot"><-</span> liftAff <span class="op">$</span> PG.withConnection pool \conn <span class="ot">-></span> PG.withTransaction conn <span class="kw">do</span></span>
<span id="cb35-18"><a href="#cb35-18" aria-hidden="true" tabindex="-1"></a> P.findUser conn userId <span class="op">>>=</span> <span class="kw">case</span> _ <span class="kw">of</span></span>
<span id="cb35-19"><a href="#cb35-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> <span class="fu">pure</span> <span class="dt">Nothing</span></span>
<span id="cb35-20"><a href="#cb35-20" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> (<span class="dt">User</span> user) <span class="ot">-></span> <span class="kw">do</span></span>
<span id="cb35-21"><a href="#cb35-21" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> user' <span class="ot">=</span> <span class="dt">User</span> (user { name <span class="ot">=</span> userName })</span>
<span id="cb35-22"><a href="#cb35-22" aria-hidden="true" tabindex="-1"></a> P.updateUser conn user'</span>
<span id="cb35-23"><a href="#cb35-23" aria-hidden="true" tabindex="-1"></a> <span class="fu">pure</span> <span class="op">$</span> <span class="dt">Just</span> user'</span>
<span id="cb35-24"><a href="#cb35-24" aria-hidden="true" tabindex="-1"></a> <span class="kw">case</span> savedUser <span class="kw">of</span></span>
<span id="cb35-25"><a href="#cb35-25" aria-hidden="true" tabindex="-1"></a> <span class="dt">Nothing</span> <span class="ot">-></span> respond <span class="dv">404</span> { <span class="fu">error</span><span class="op">:</span> <span class="st">"User not found with id: "</span> <span class="op"><></span> sUserId }</span>
<span id="cb35-26"><a href="#cb35-26" aria-hidden="true" tabindex="-1"></a> <span class="dt">Just</span> user <span class="ot">-></span> respond <span class="dv">200</span> (encode user)</span></code></pre></div>
<p>After checking for a valid user ID as before, we get the decoded request body as a <code>UserPatch</code> instance. If the path does not have the <code>name</code> field or has it as <code>null</code>, there is nothing to do and we respond with a 204 status. If the user’s name is present in the patch, we validate it for non-emptiness. Then, within a database transaction, we try to find the user with the given ID, responding with a 404 status if the user is not found. If the user is found, we update the user’s name in the database, and respond with a 200 status and the saved user encoded as the JSON response body.</p>
<p>Finally, we can add the route to our server’s router in <code>src/SimpleService/Server.purs</code> to make the functionality available:</p>
<div class="sourceCode" id="cb36" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb36-1"><a href="#cb36-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb36-2"><a href="#cb36-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.App</span> (<span class="dt">App</span>, delete, get, http, listenHttp, post, useExternal)</span>
<span id="cb36-3"><a href="#cb36-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">Node.Express.Types</span> (<span class="dt">EXPRESS</span>, <span class="dt">Method</span>(..))</span>
<span id="cb36-4"><a href="#cb36-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Handler</span> (createUser, deleteUser, getUser, updateUser)</span>
<span id="cb36-5"><a href="#cb36-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb36-6"><a href="#cb36-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb36-7"><a href="#cb36-7" aria-hidden="true" tabindex="-1"></a><span class="ot">app ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">App</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb36-8"><a href="#cb36-8" aria-hidden="true" tabindex="-1"></a>app pool <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb36-9"><a href="#cb36-9" aria-hidden="true" tabindex="-1"></a> useExternal jsonBodyParser</span>
<span id="cb36-10"><a href="#cb36-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb36-11"><a href="#cb36-11" aria-hidden="true" tabindex="-1"></a> get <span class="st">"/v1/user/:id"</span> <span class="op">$</span> getUser pool</span>
<span id="cb36-12"><a href="#cb36-12" aria-hidden="true" tabindex="-1"></a> delete <span class="st">"/v1/user/:id"</span> <span class="op">$</span> deleteUser pool</span>
<span id="cb36-13"><a href="#cb36-13" aria-hidden="true" tabindex="-1"></a> post <span class="st">"/v1/users"</span> <span class="op">$</span> createUser pool</span>
<span id="cb36-14"><a href="#cb36-14" aria-hidden="true" tabindex="-1"></a> patch <span class="st">"/v1/user/:id"</span> <span class="op">$</span> updateUser pool</span>
<span id="cb36-15"><a href="#cb36-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb36-16"><a href="#cb36-16" aria-hidden="true" tabindex="-1"></a> patch <span class="ot">=</span> http (<span class="dt">CustomMethod</span> <span class="st">"patch"</span>)</span></code></pre></div>
<p>We can try it out now:</p>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/user/1
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 26
Content-Type: application/json; charset=utf-8
Date: Fri, 11 Sep 2017 06:41:10 GMT
ETag: W/"1a-hoLBx55zeY8nZFWJh/kM05pXwSA"
X-Powered-By: Express
{
"id": 1,
"name": "abhinav"
}</code></pre>
<pre class="plain"><code>$ http PATCH http://localhost:4000/v1/user/1 name=abhinavsarkar
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 31
Content-Type: application/json; charset=utf-8
Date: Fri, 11 Sep 2017 06:41:36 GMT
ETag: W/"1f-EG5i0hq/hYhF0BsuheD9hNXeBpI"
X-Powered-By: Express
{
"id": 1,
"name": "abhinavsarkar"
}</code></pre>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/user/1
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 31
Content-Type: application/json; charset=utf-8
Date: Fri, 11 Sep 2017 06:41:40 GMT
ETag: W/"1f-EG5i0hq/hYhF0BsuheD9hNXeBpI"
X-Powered-By: Express
{
"id": 1,
"name": "abhinavsarkar"
}</code></pre>
<pre class="plain"><code>$ http PATCH http://localhost:4000/v1/user/1
HTTP/1.1 204 No Content
Connection: keep-alive
Date: Fri, 11 Sep 2017 06:42:31 GMT
X-Powered-By: Express</code></pre>
<pre class="plain"><code>$ http PATCH http://localhost:4000/v1/user/1 name=""
HTTP/1.1 422 Unprocessable Entity
Connection: keep-alive
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Fri, 11 Sep 2017 06:43:17 GMT
ETag: W/"27-JQsh12xu/rEFdWy8REF4NMtBUB4"
X-Powered-By: Express
{
"error": "User name must not be empty"
}</code></pre>
<div class="page-break">
</div>
<h3 id="listing-all-users">Listing all Users</h3>
<p>Listing all users is quite simple since it doesn’t require us to take any request parameter.</p>
<p>We add the handler to the <code>src/SimpleService/Handler.purs</code> file:</p>
<div class="sourceCode" id="cb42" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb42-1"><a href="#cb42-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb42-2"><a href="#cb42-2" aria-hidden="true" tabindex="-1"></a><span class="ot">listUsers ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">Handler</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb42-3"><a href="#cb42-3" aria-hidden="true" tabindex="-1"></a>listUsers pool <span class="ot">=</span> liftAff (PG.withConnection pool P.listUsers) <span class="op">>>=</span> encode <span class="op">>>></span> respond <span class="dv">200</span></span></code></pre></div>
<p>And the route to the <code>src/SimpleService/Server.purs</code> file:</p>
<div class="sourceCode" id="cb43" data-lang="purescript"><pre class="sourceCode haskell numberSource"><code class="sourceCode haskell"><span id="cb43-1"><a href="#cb43-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb43-2"><a href="#cb43-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="dt">SimpleService.Handler</span> (createUser, deleteUser, getUser, listUsers, updateUser)</span>
<span id="cb43-3"><a href="#cb43-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- previous code</span></span>
<span id="cb43-4"><a href="#cb43-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb43-5"><a href="#cb43-5" aria-hidden="true" tabindex="-1"></a><span class="ot">app ::</span> <span class="kw">forall</span> eff<span class="op">.</span> <span class="dt">PG.Pool</span> <span class="ot">-></span> <span class="dt">App</span> (<span class="ot">postgreSQL ::</span> <span class="dt">PG.POSTGRESQL</span> <span class="op">|</span> eff)</span>
<span id="cb43-6"><a href="#cb43-6" aria-hidden="true" tabindex="-1"></a>app pool <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb43-7"><a href="#cb43-7" aria-hidden="true" tabindex="-1"></a> useExternal jsonBodyParser</span>
<span id="cb43-8"><a href="#cb43-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb43-9"><a href="#cb43-9" aria-hidden="true" tabindex="-1"></a> get <span class="st">"/v1/user/:id"</span> <span class="op">$</span> getUser pool</span>
<span id="cb43-10"><a href="#cb43-10" aria-hidden="true" tabindex="-1"></a> delete <span class="st">"/v1/user/:id"</span> <span class="op">$</span> deleteUser pool</span>
<span id="cb43-11"><a href="#cb43-11" aria-hidden="true" tabindex="-1"></a> post <span class="st">"/v1/users"</span> <span class="op">$</span> createUser pool</span>
<span id="cb43-12"><a href="#cb43-12" aria-hidden="true" tabindex="-1"></a> patch <span class="st">"/v1/user/:id"</span> <span class="op">$</span> updateUser pool</span>
<span id="cb43-13"><a href="#cb43-13" aria-hidden="true" tabindex="-1"></a> get <span class="st">"/v1/users"</span> <span class="op">$</span> listUsers pool</span>
<span id="cb43-14"><a href="#cb43-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">where</span></span>
<span id="cb43-15"><a href="#cb43-15" aria-hidden="true" tabindex="-1"></a> patch <span class="ot">=</span> http (<span class="dt">CustomMethod</span> <span class="st">"patch"</span>)</span></code></pre></div>
<p>And that’s it. We can test this endpoint:</p>
<pre class="plain"><code>$ http POST http://localhost:4000/v1/users id:=2 name=sarkarabhinav
HTTP/1.1 201 Created
Connection: keep-alive
Content-Length: 0
Date: Fri, 11 Sep 2017 07:06:24 GMT
X-Powered-By: Express</code></pre>
<pre class="plain"><code>$ http GET http://localhost:4000/v1/users
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 65
Content-Type: application/json; charset=utf-8
Date: Fri, 11 Sep 2017 07:06:27 GMT
ETag: W/"41-btt9uNdG+9A1RO7SCLOsyMmIyFo"
X-Powered-By: Express
[
{
"id": 1,
"name": "abhinavsarkar"
},
{
"id": 2,
"name": "sarkarabhinav"
}
]</code></pre>
<h2 data-track-content data-content-name="conclusion" data-content-piece="ps-simple-rest-service" id="conclusion">Conclusion</h2>
<p>That concludes the first part of the two-part tutorial. We learned how to set up a PureScript project, how to access a Postgres database and how to create a JSON REST API over the database. The code till the end of this part can be found in <a href="https://github.com/abhin4v/ps-simple-rest-service/tree/9fdfe3a15508a3c29bd4bc96310fcf52b1022678" target="_blank" rel="noopener">github</a>. In the <a href="https://abhinavsarkar.net/posts/ps-simple-rest-service-2/?mtm_campaign=feed">next</a> part, we’ll learn how to do API validation, application configuration and logging.</p><p>If you liked this post, please <a href="https://abhinavsarkar.net/posts/ps-simple-rest-service/?mtm_campaign=feed#syndications">leave a comment</a>.</p><img referrerpolicy="no-referrer-when-downgrade" src="https://anna.abhinavsarkar.net/matomo.php?idsite=1&rec=1" style="border:0" alt="" />2017-09-29T00:00:00Z<p>At <a href="https://nilenso.com" target="_blank" rel="noopener">Nilenso</a>, we’ve been working with a client who has chosen <a href="http://purescript.org" target="_blank" rel="noopener">PureScript</a> as their primary programming language. Since I couldn’t find any canonical documentation on writing a web service in PureScript, I thought I’d jot down the approach that we took.</p>
<p>The aim of this two-part tutorial is to create a simple JSON <a href="https://en.wikipedia.org/wiki/REST" target="_blank" rel="noopener">REST</a> web service written in PureScript, to run on a node.js server.