How to combine Servant and React Admin, part 3 : Display a single comment

Kasper Janssens
Jun 24, 2020 · 6 min read

Overview

Our last blog ended with a very simple REST server and a UI that could list comments, and that was it. Let’s expand it a bit with GET requests to fetch single comments!

GET resource in Servant

The GET request itself is reasonably close to what we already have. Let’s recall our current type :

type WebApi = "comments" :> Get '[ JSON] (ListHeaders [Comment])
type WebApi
= "comments" :> Get '[ JSON] (ListHeaders [Comment])
:<|> "comments" :> Capture "id" Int :> Get '[ JSON] Comment

Implement the server

If we would try to compile now we would see this error :

* Couldn't match type 'Handler (ListHeaders [Comment])' with 'Handler (Headers ' [TotalCountHeader] [Comment]) :<|> (Int -> Handler Comment)' Expected type: Server WebApi Actual type: Handler (ListHeaders [Comment])
module Server where

import Servant
import ApiType
import Network.Wai.Handler.Warp (run)
import Comment
import Cors
import qualified Data.List as L
import Data.Maybe (fromJust)

fixedComments :: [Comment]
fixedComments = [Comment 1 "A comment", Comment 2 "Another comment"]

listComments :: Handler (ListHeaders [Comment])
listComments = return $ addHeader 2 fixedComments

getComment :: Int -> Handler Comment
getComment requestedId =
let maybeComment = L.find (\comment -> Comment.id comment == requestedId) fixedComments in
return $ fromJust maybeComment

server :: Server WebApi
server = listComments :<|> getComment
curl -i http://localhost:8082/comments/asdf HTTP/1.1 400 Bad Request Transfer-Encoding: chunked Date: Sat, 30 May 2020 09:46:36 GMT Server: Warp/3.3.10

The union operator

This server snippet above boils down to implementing the server api we have defined with the union operator by applying that same union operator on functions that have the correct signature (corresponding to the one we used in the definition).

Error handling

Currently, we will return a 500 Internal Server error in case the GET request cannot retrieve our comment. This is because the fromJust function we apply will throw an exception, that will be translated by the server loop into a 500.

getComment :: Int -> Handler Comment
getComment requestedId =
let maybeComment = L.find (\comment -> Comment.id comment == requestedId) fixedComments in
maybe (throwError err404) return maybeComment
err404 :: ServerError
err404 = ServerError { errHTTPCode = 404
, errReasonPhrase = "Not Found"
, errBody = ""
, errHeaders = []
}
getComment :: Int -> Handler Comment
getComment requestedId =
let maybeComment = L.find (\comment -> Comment.id comment == requestedId) fixedComments in
maybe (throwError err404 {errBody = BSLazy.pack $ "Could not retrieve comment with id " ++ show requestedId}) return maybeCommen
curl -i http://localhost:8082/comments/2 HTTP/1.1 200 OK Transfer-Encoding: chunked Date: Sat, 30 May 2020 09:46:25 GMT Server: Warp/3.3.10 Content-Type: application/json;charset=utf-8 { "content":"Another comment","id":2}
curl -i http://localhost:8082/comments/3 HTTP/1.1 404 Not Found Transfer-Encoding: chunked Date: Sat, 30 May 2020 09:46:26 GMT Server: Warp/3.3.10 Could not retrieve comment with id 3

GET from React Admin

Let’s head back to React Admin now. We want to show our comment, and React Admin has a handy feature where it can guess the layout of the show component based on the JSON being returned.

const App = () => (
<Admin dataProvider={dataProvider}>
<Resource name="comments" list={CommentsList} show={ShowGuesser}/>
</Admin>
);
Guessed Show:

export const CommentShow = props => (
<Show {...props}>
<SimpleShowLayout>
<TextField source="content" />
<TextField source="id" />
</SimpleShowLayout>
</Show>
);
export const CommentsShow = props => (
<Show {...props}>
<SimpleShowLayout>
<TextField source="id"/>
<TextField source="content"/>
</SimpleShowLayout>
</Show>
);
<Resource name="comments" list={CommentsList} show={CommentsShow}/>

Conclusion

Okay, this wraps up a single GET request. On to POST in the next blog.

Dev Genius

Coding, Tutorials, News, UX, UI and much more related to development

Sign up for Best Stories

By Dev Genius

The best stories sent monthly to your email. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store