{"id":15674260,"url":"https://github.com/k-bx/owlcloud","last_synced_at":"2025-11-09T19:31:29.520Z","repository":{"id":142824264,"uuid":"42322235","full_name":"k-bx/owlcloud","owner":"k-bx","description":"OwnCloud for owls done via The Microservice Architecture","archived":false,"fork":false,"pushed_at":"2017-11-25T11:04:42.000Z","size":35,"stargazers_count":236,"open_issues_count":2,"forks_count":13,"subscribers_count":12,"default_branch":"master","last_synced_at":"2024-12-10T02:20:23.871Z","etag":null,"topics":["haskell","microservice","servant"],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/k-bx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-09-11T17:18:43.000Z","updated_at":"2024-11-10T18:50:05.000Z","dependencies_parsed_at":"2023-08-23T23:47:20.575Z","dependency_job_id":null,"html_url":"https://github.com/k-bx/owlcloud","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k-bx%2Fowlcloud","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k-bx%2Fowlcloud/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k-bx%2Fowlcloud/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k-bx%2Fowlcloud/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/k-bx","download_url":"https://codeload.github.com/k-bx/owlcloud/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230431103,"owners_count":18224655,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["haskell","microservice","servant"],"created_at":"2024-10-03T15:43:54.729Z","updated_at":"2025-11-09T19:31:29.485Z","avatar_url":"https://github.com/k-bx.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"Type-Safe Microservices in Haskell with Servant\n===============================================\n\n10.05.2016 NOTE! The post was updated with the latest servant-0.7\ncode, but some bugs might be still left. PRs are welcome!\n\nMicroservices were becoming a hot thing few years ago and it seems\n[they are still on the rise](http://philcalcado.com/2015/09/08/how_we_ended_up_with_microservices.html).\n\nIt always surprised me how brave developers are: at one moment they\njust decide to introduce hundreds contracts of network-separated APIs\nwithout having any static proof that it \"works well together\". I have\nno idea how others are solving this problem, but was interested in\ntrying it out in Haskell.\n\nThis repository is a tutorial (with working code) of super-small\nservice called OwlCloud. It is implemented mostly via\n[Servant](http://haskell-servant.github.io/) framework.\n\nI will try to explain how things look like for those who don't do\nHaskell every day (I believe most Haskellers could easily do the same\nanyway).\n\nWARNING! While being small, the service is still almost real-world,\ncontains a lot of features, so it has quite a lot of boilerplate. The\npurpose is to show that it's not THAT much boilerplate as for a\nservice having all these features.\n\n![woop](http://i.imgur.com/1CnO4rN.jpg)\n\nArchitecture overview\n---------------------\n\n```\n                                      +--------------------------------------+   +----------------+\n                                 +---\u003e+ Users microservice (/api/users/*)    +---\u003e Users storage  |\n                                 |    +-------------+------------------------+   +----------------+\n                                 |                  ^\n+-----------+      +-------------+                  |\n|  Request  +-----\u003e+ Front End  ||                  |\n+-----------+      +-------------+                  |\n                                 |                  |\n                                 |    +-------------+------------------------+   +----------------+\n                                 +---\u003e+ Albums microservice (/api/albooms/*) +---\u003e Albums storage |\n                                      +--------------------------------------+   +----------------+\n```\n\nRequest hits a Front-End. Front-end will act as a proxy which only\nknows prefix of each microservice's public part of API (starts with\n`/api/`), and proxies request to it. It won't try to do any other job.\n\nEach microservice is a REST app, which has public (`/api/*`) and\nprivate (`/private-api/*`) parts. Private parts don't do any\nsecurity-checks regarding their requestor, as they're assumed to be\ninside secured network. Additional security could be done in future if\nneeded.\n\nUsers microservice will have these end-points:\n\n- `/api/users/owl-in/` -- accept POST-request with a json-document\n  like `{\"whoo\": \"user\", \"passwoord\": \"who?\"}`, and return signin\n  token (a string)\n- `/api/users/owl-out` -- accepts HTTP header `Authorization` with\n  token to check a user, and signs them out (forgets this token)\n- `/private-api/users/token-validity/:token` -- returns a validity\n  response if token is still good or not. This is an example of inner\n  API, used by other microservices to not work with database directly,\n  but rather ask this microservice if user's token is valid\n\nAlbums microservice will look quite simple:\n\n- `/api/albooms/` -- will check user for a correct `Authorization`\n  header via Users microservice, and in case of success will render\n  list of albums with photos inside (should I create a microservice\n  for photos to make things more interesting?)\n\n  Should accept `sortby` query parameter, which is either `whoolest`\n  (owl for \"coolest\"), or `date`.\n\nInstallation and Running\n------------------------\n\nIf you're curious to play a bit with this repo's code, here are the\ninstructions.\n\n1. Install [haskell stack tool](https://github.com/commercialhaskell/stack).\n2. Run `stack build` inside project root (right next to this README)\n\nTo run, open 3 terminals and run these commands in them:\n\n```\nstack exec owlcloud-front\nstack exec owlcloud-users\nstack exec owlcloud-albums\n```\n\nAlternatively, if you have my [par](https://github.com/k-bx/par) tool\ninstalled, you can just run:\n\n```\npar \"stack exec owlcloud-front\" \"stack exec owlcloud-users\" \"stack exec owlcloud-albums\"\n```\n\nIf stack doesn't work for you, you can try [cabal-only-instructions](https://gist.github.com/k-bx/ff100755eaa12f950e9a).\n\nCode overview: projects layout\n------------------------------\n\nThere are 4 cabal projects in root of this project: `owlcloud-front`,\n`owlcloud-lib`, `owlcloud-users` and `owlcloud-albums`.\n\n`-front` will correspond to front-end proxy, `-lib` contains shared\ncode like routes, types, and common utilities.\n\n`-users` and `-albums` are purely microservices.\n\nRoutes\n------\n\nIn Servant, type-safety of your REST API is taken to the extreme. This\nmeans that type expresses not only path of your route, but also types\nof its dynamic pieces, query-parameters, type of data passed through\nrequest-body, expected headers, format and type of\nreturn-value. Sounds impressive, isn't it?\n\nBut how does it look like, exactly? Well, it's far from looking as an\nintuitive DSL, but it's good-enough to not want to write one, imho.\n\nThe code for routes resides in\n[owlcloud-lib/src/OwlCloud/Types.hs](owlcloud-lib/src/OwlCloud/Types.hs).\n\nServant API expects you to first describe routes \"in types\", and then\nconnect them with your routes where you want to. So, you can\n\"implement\" your routes several times, just as you can write multiple\nfunctions of some type. This lets us describe API for each\nmicroservice, and then combine them in bigger API.\n\n```haskell\ntype OwlCloudAPI = UsersAPI :\u003c|\u003e AlbumsAPI\n```\n\nThis is a central type, which describes all our APIs. We have two\ntype-synonyms for each microservice, and then smash them together with\na type-level combinator `:\u003c|\u003e`. Using type-synonyms means that our\ntype-errors might (and will!) become nasty, and rather suitable for\nexperienced haskeller's brain, but nothing very special to Servant is\nneeded, just a general Haskell type-error-resolving experience.\n\nWe don't actually use this type, since our front-end proxy just blindly\nproxies requests by their prefixes, but having `OwlCloudAPI` might be\nuseful for documentation and type-checking purposes. You might also\ncombine your microservices in one big service for purposes of testing\nlocally, but it's not in scope of this tutorial.\n\nSo, types for Users microservice will look like this:\n\n```haskell\ntype UsersAPI =\n  \"api\" :\u003e \"users\" :\u003e \"owl-in\" :\u003e ReqBody '[JSON] LoginReq :\u003e Post '[JSON] SigninToken :\u003c|\u003e\n  \"api\" :\u003e \"users\" :\u003e Authorized (\"owl-out\" :\u003e Post '[JSON] ()) :\u003c|\u003e\n  \"private-api\" :\u003e \"users\" :\u003e \"token-validity\" :\u003e Capture \"token\" SigninToken :\u003e Get '[JSON] TokenValidity\n\nnewtype SigninToken = SigninToken Text\n    deriving (ToJSON, FromJSON, FromHttpApiData, ToHttpApiData, Ord, Eq)\n\ndata LoginReq = LoginReq\n    { whoo      :: Text\n    , passwoord :: Text }\n    deriving (Generic)\n\ndata TokenValidity = TokenValidity\n    { isValid :: Bool }\n    deriving (Generic, Show)\n\ninstance FromJSON LoginReq\ninstance ToJSON LoginReq\ninstance FromJSON TokenValidity\ninstance ToJSON TokenValidity\n```\n\nThat's a big piece of code. Let's look closer.\n\nAgain, we have individual routes combined together with `:\u003c|\u003e` at the\nend of each line.\n\nFirst route looks like this:\n\n```haskell\n  \"api\" :\u003e \"users\" :\u003e \"owl-in\" :\u003e ReqBody '[JSON] LoginReq :\u003e Post '[JSON] SigninToken\n```\n\nIt corresponds (as you might have guessed) to route\n`/api/users/owl-in`. `ReqBody '[JSON] LoginReq` tells that Servant\nwill take a request body, requiring a `Content-Type: application/json`\nheader in your request, decode it as a `JSON` decoder (it can support\nmultiple, if you put more in list) into `LoginReq` type, and pass it\nas a parameter to your handler, which we'll see later.\n\n`Post '[JSON] SigninToken` tells us that we'll respond to\n`POST`-reuqest, we'll respond in `JSON` with a `SigninToken` datatype.\n\nWow, whole bunch of information about our route, and all that encoded\nin mostly-readable type-level representation. Neat!\n\nSecond route:\n\n```haskell\n  \"api\" :\u003e \"users\" :\u003e Authorized (\"owl-out\" :\u003e Post '[JSON] ())\n```\n\nEverything should be clear except that `Authorized` function-like\nthing. What's that? It's a type-synonym I defined at the bottom of the\nsame file:\n\n```haskell\ntype Authorized t = Header \"Authorization\" SigninToken :\u003e t\n```\n\nSo, if you replace a type-synonym, your route will look like:\n\n```haskell\n  \"api\" :\u003e \"users\" :\u003e Header \"Authorization\" SigninToken :\u003e \"owl-out\" :\u003e Post '[JSON] ()\n```\n\nEvery resource, which wants to access data of some user, will have to\nsend a `SigninToken`, which is just a newtype around `Text`, and put\nit under `Authorization` header.\n\nLast route is:\n\n\n```haskell\n  \"private-api\" :\u003e \"users\" :\u003e \"token-validity\" :\u003e Capture \"token\" SigninToken :\u003e Get '[JSON] TokenValidity\n```\n\nNew part is `Capture` here. It just tells that we have a dynamic part\nof a route `/private-api/users/token-validity/\u003cdynamic-part-here\u003e`,\nwhich will be captured and passed as a param into our handler.\n\nAlbums microservice types should look quite familiar now:\n\n```haskell\ntype AlbumsAPI =\n  \"api\" :\u003e \"albooms\" :\u003e Authorized (QueryParam \"sortby\" SortBy :\u003e Get '[JSON] [Album])\n\ndata Album = Album [Photo]\n    deriving (Generic)\n\ndata Photo = Photo\n    { description :: Text\n    , image       :: URL }\n    deriving (Generic)\n\ndata SortBy\n    = SortByWhoolest\n    | SortByDate\n\ninstance FromJSON Photo\ninstance ToJSON Photo\ninstance FromJSON Album\ninstance ToJSON Album\ninstance FromHttpApiData SortBy where\n    parseQueryParam \"whoolest\" = Right SortByWhoolest\n    parseQueryParam \"date\" = Right SortByDate\n    parseQueryParam x = Left (\"Unknown sortby value:\" \u003c\u003e x)\ninstance ToHttpApiData SortBy where\n    toQueryParam SortByWhoolest = \"whoolest\"\n    toQueryParam SortByDate = \"date\"\n\n```\n\nWe capture a `sortby` param, which you will pass as `?sortby=date` at\nthe end of your url.\n\nHere you can also see manual implementation of `FromHttpApiData`\ntype-class: it's used to encode/decode url piece value into your\ndatatype.\n\nHandlers\n--------\n\nWe've covered routes, now we can show how our handlers look\nlike. Let's begin with a Users microservice.\n\nCode resides at\n[./owlcloud-users/src/Main.hs](owlcloud-users/src/Main.hs) file.\n\nLet's begin with some machinery to combine individual handlers into\n`UsersAPI` type, and then generation of a\n[wai](http://hackage.haskell.org/package/wai) `Application` type. WAI\nis a set of contracts, which describe a \"reusable haskell web\napplication interface\". It's similar to Python's WSGI, if you're\nfamiliar with that. After you have a WAI `Application`, you can run it\nwith a haskell web-server of your choice. We'll use\n[warp](http://hackage.haskell.org/package/warp), which is as fast as\nnginx (and sometimes faster!).\n\n```haskell\nserver :: Server UsersAPI\nserver = owlIn :\u003c|\u003e owlOut :\u003c|\u003e tokenValidity\n\nusersAPI :: Proxy UsersAPI\nusersAPI = Proxy\n\napp :: Application\napp = serve usersAPI server\n```\n\nFirst thing to notice is that we use a new operator `:\u003c|\u003e` from\nServant, which is a value-level operator (never confuse with `:\u003c|\u003e`,\nhaha). It combines individual handlers together, and type-system then\nchecks that type of overall expression matches `UsersAPI`. Errors are\nsomewhat big, as type-synonyms are expanded with not too much help to\nus, but if you'll look careful enough -- you'll be able to figure\nthing out.\n\nNow, to individual handlers:\n\n```haskell\nowlIn :: LoginReq -\u003e ExceptT ServantErr IO SigninToken\nowlIn LoginReq{..} =\n    case (whoo, passwoord) of\n      (\"great horned owl\", \"tiger\") -\u003e do\n          uuid \u003c- liftIO UUID.nextRandom\n          let token = SigninToken (UUID.toText uuid)\n          liftIO $ atomically $\n              modifyTVar db $ \\s -\u003e\n                s { validTokens = Set.insert token (validTokens s) }\n          return token\n      _ -\u003e throwE (ServantErr 400 \"Username/password pair did not match\" \"\" [])\n```\n\nThey start with our `/api/users/owl-in` handler. We begin with\nsomething which amazes me about Servant already: you get your\nroute-parameters as...function parameters!\n\nSo, no more silly manual extraction of data from some big `Request`\ntype: you get what you asked for, and you get it via function\nparameters. Servant handles the rest for you.\n\nNow, since we don't use a real database for purpose of this tutorial,\nwe'll just allow single login/password pair: (\"great horned owl\",\n\"tiger\"). If it matches, we are generating a `SigninToken` and put it\ninto a global STM-variable `db`. It resides inside\n[Common.hs](./owlcloud-lib/src/OwlCloud/Common.hs), if you're interested\nlooking at actual implementation, but in real-world app it'll probably\njust be a database. For curious, type of our database is this:\n\n```haskell\ndata State = State\n    { validTokens :: Set SigninToken\n    , albumsList  :: [Album] }\n\ndb :: TVar State\ndb = unsafePerformIO (unsafeInterleaveIO (newTVarIO (State Set.empty initialAlbums)))\n```\n\nYes, we use a global variable in Haskell, and sometimes it makes\nsense, and it's dangerous (as indicated by the scary names).\n\nIf you enter wrong credentials, we will respond with a `400`-code\nerror, and a help-message describing the reason. You can add some\nresponse body, and additional headers if you want to, but I don't.\n\nError is returned in this interesting way:\n\n```haskell\nthrowE (ServantErr 400 \"Username/password pair did not match\" \"\" [])\n```\n\nThis\n[throwE](http://hackage.haskell.org/package/transformers/docs/Control-Monad-Trans-Except.html#v:throwE)\ncombinator from `transformers` package, is something which converts\nsome error-type `e` into a `ExceptT e m a` type. The reason we're\nusing it is because Servant uses type `ExceptT ServantErr IO a` for\nour handlers. It's a small\n[Monad Transformer](https://github.com/kqr/gists/blob/master/articles/gentle-introduction-monad-transformers.md)\nstack on top of IO, which allows explicit short-circuiting via\n`ServantErr` type, denoting failure.\n\nOur `owl-out` handler just removes your token from our imaginary database:\n\n```haskell\nowlOut :: Maybe SigninToken -\u003e ExceptT ServantErr IO ()\nowlOut mt = do\n    checkAuth mt\n    maybe (return ()) out mt\n  where\n    out token = liftIO $ atomically $ modifyTVar db $ \\s -\u003e\n                  s { validTokens = Set.delete token (validTokens s) }\n\ncheckAuth :: Maybe SigninToken -\u003e ExceptT ServantErr IO ()\ncheckAuth = maybe unauthorized runCheck\n  where\n    runCheck (SigninToken token) = do\n        state \u003c- liftIO $ atomically $ readTVar db\n        let isMember = Set.member (SigninToken token) (validTokens state)\n        unless isMember unauthorized\n    unauthorized =\n        throwE (ServantErr 401 \"You are not authenticated. Please sign-in\" \"\" [])\n```\n\nLast handler is an inner API to check token validity:\n\n```haskell\ntokenValidity :: SigninToken -\u003e ExceptT ServantErr IO TokenValidity\ntokenValidity token = do\n    state \u003c- liftIO $ atomically $ readTVar db\n    return (TokenValidity (Set.member token (validTokens state)))\n```\n\nFinally, we run our app on port `8082`. Of course, this should be\nstored in some environment variable or config in real-world:\n\n```haskell\nmain :: IO ()\nmain = run 8082 app\n```\n\nWe use `run` from Warp web-server mentioned before.\n\nThe Albums microservice shouldn't be much harder to understand. Just\none end-point, no need for glueing with `:\u003c|\u003e` operator:\n\n```haskell\nserver :: Manager -\u003e Server AlbumsAPI\nserver = albums\n```\n\nAlso notice that we'll need to pass a `Manager` value in order to have\nconnection-pooling and caching when we speak to other\nmicroservices. It's created in main and just passed in parameters.\n\nHandler:\n\n```haskell\nalbums :: Manager -\u003e Maybe SigninToken -\u003e Maybe SortBy -\u003e ExceptT ServantErr IO [Album]\nalbums mgr mt sortBy = do\n    checkValidity mgr mt\n    state \u003c- liftIO $ atomically $ readTVar db\n    return (albumsList state)\n```\n\nWe don't do any actual sorting here, GHC will tell us about this via\nWarning of unused `sortBy` variable (how many frameworks tell you your\nGET-parameters from your API description are not used?).\n\nNow, the interesting part is the `checkValidity` function. We put it\nin `Common.hs`, since it'd be reused by other microservices in the\nfuture. It will do a request to the Users microservice, check the validity of\na token, and show an error if needed.\n\n```haskell\ncheckValidity :: Manager\n              -\u003e Maybe SigninToken\n              -\u003e ExceptT ServantErr IO ()\ncheckValidity mgr =\n    maybe (throwE (ServantErr 400 \"Please, provide an authorization token\" \"\" []))\n          (\\t -\u003e fly (apiUsersTokenValidity t mgr usersBaseUrl) \u003e\u003e= handleValidity)\n  where\n    handleValidity (TokenValidity True) = return ()\n    handleValidity (TokenValidity False) =\n        throwE (ServantErr 400 \"Your authorization token is invalid\" \"\" [])\n```\n\nYou already understand all the `left ...` parts which just return\nerrors. But what's `fly (apiUsersTokenValidity t)`, exactly?\n\nLet's make a new sub-header in this tutorial so it's easier to find.\n\nRequesting other microservices\n------------------------------\n\nServant gives you a mechanism to request other services in a type-safe\nmanner. What you need to do, is to \"unpack\" your type-level API\ndefinition into individual request-routes (also in [Common.hs](./owlcloud-lib/src/OwlCloud/Common.hs)):\n\n```haskell\napiUsersOwlIn :\u003c|\u003e apiUsersOwlOut :\u003c|\u003e apiUsersTokenValidity =\n    client (Proxy::Proxy UsersAPI)\n\napiAlbumsList =\n    client (Proxy::Proxy AlbumsAPI)\n```\n\nThe scary `Proxy::Proxy UsersAPI` part is just to move things from\ntype-level to value-level world. This is usually done when you're\nalready able to extract all needed information from just a type, but\nyou need to do some actions with it at the value-level world.\n\nSo, we deconstructed some special-built structure (via `client`\nfunction) into individual routines, which are able to request other\nmicroservices.\n\nTheir types take usual route arguments, and are returning something of\ntype `ExceptT ServantError m a`. So, these values are not some\ndescriptions, but rather actions themselves, and they do the hard job\nof requesting microservices for you. Cool!\n\nNotice the\n[`ServantError`](hackage.haskell.org/package/servant-client/docs/Servant-Client.html#t:ServantError)\ntype. It's not the\n[`ServantErr`](http://hackage.haskell.org/package/servant-server/docs/Servant-Server-Internal-ServantErr.html)\ntype we've seen before, used to short-circuit from handler. Rather,\nit's a REST-client-response error, which might happen if, say, your\nmicroservice is down or responded with an error.\n\nSo we implement a special `fly` function, which will convert the response\nfrom one possible error (microservice-request error) into another: the one\nwhich we will send to our users, plus some logging.\n\n```haskell\nfly :: (Show b, MonadIO m)\n    =\u003e ExceptT ServantError m b\n    -\u003e ExceptT ServantErr m b\nfly apiReq = do\n  res \u003c- lift (runExceptT apiReq)\n  either logAndFail return res\n  where\n    logAndFail e = do\n        liftIO (putStrLn (\"Got internal-api error: \" ++ show e))\n        throwE internalError\n    internalError = ServantErr 500 \"CyberInternal MicroServer MicroError\" \"\" []\n```\n\nThere you go, now you know how that type-safe microservice-requesting\nmachinery works. Wasn't that hard, wasn't it!\n\nLast bit: front-end\n-------------------\n\nNow, the last bit is to write a front-end. Code is located at\n[owlcloud-front/src/Main.hs](./owlcloud-front/src/Main.hs) file.\n\nI admit, I didn't implement a bullet-proof fully-functional proxy\nwhich handles everything in a streaming fashion (sombody, please do\nso, would be a useful tutorial, and shouldn't take too much code),\nit's just not what I intend to do in this tutorial, but this one\nshouldn't be bad in terms of performance.\n\nWe define our app as:\n\n```haskell\napp :: Manager -\u003e Application\napp mgr req respond =\n    case pathInfo req of\n        (\"api\":\"users\":_) -\u003e microservice \"http://localhost:8082/\"\n        (\"api\":\"albooms\":_) -\u003e microservice \"http://localhost:8083/\"\n        _ -\u003e respond (responseLBS status404 [] \",,,(o,o),,,\\n ';:`-':;' \\n   -\\\"-\\\"-   \\n\")\n  where\n    microservice = microserviceProxy mgr req respond\n```\n\nWe look at request-path, if it begins with `/api/users`, we\nmicro-forward it to \"http://localhost:8082/\" (hardcode!). Same for\n`/api/albooms` end-point.\n\nWe use a [wreq](http://hackage.haskell.org/package/wreq) package to do\nactual requests:\n\n```haskell\nmicroserviceProxy :: Manager -\u003e Request -\u003e (Network.Wai.Response -\u003e IO b) -\u003e Text\n                  -\u003e IO b\nmicroserviceProxy mgr req respond basePath = do\n    let opts = W.defaults \u0026 W.manager .~ Right mgr\n                          \u0026 W.headers .~ requestHeaders req\n                          \u0026 W.params .~ getReqParams req\n        url = basePath \u003c\u003e T.intercalate \"/\" (pathInfo req)\n    tryProxying opts url `catch` onErr\n  where\n    tryProxying opts url = do\n      r \u003c- case requestMethod req of\n             \"GET\" -\u003e W.getWith opts (toString url)\n             \"POST\" -\u003e requestBody req \u003e\u003e= W.postWith opts (toString url)\n      respond (responseLBS (r ^. W.responseStatus) (r ^. W.responseHeaders)\n                 (r ^. W.responseBody))\n    onErr (StatusCodeException s hdrs _) = respond (responseLBS s hdrs \"\")\n    onErr e = do\n      putStrLn (\"Internal error: \" ++ show e)\n      respond (responseLBS status500 [] \"Internal server error\")\n```\n\nWe just re-build a request from the request we received ourselves, and\nthen respond with the response we receive. We implement `GET` and\n`POST` methods only, but you've got the idea for others.\n\nLast bit -- running our front-end:\n\n```haskell\nmain :: IO ()\nmain = do\n    mgr \u003c- newManager defaultManagerSettings\n    run 8081 (app mgr)\n```\n\nWe create a `wreq` manager, which handles keep-alived connections (to\nnot re-connect to microservice on each request) for us, and just run\nthe web-server.\n\nThat's it. That was easy, wasn't it?\n\nTesting\n-------\n\nLet us look how it works.\n\n```\n➜  ~  curl -i -XGET -H \"Content-Type: application/json\" -H \"Authorization: badtoken\" localhost:8083/api/albooms/\nHTTP/1.1 400 Your authorization token is invalid\nTransfer-Encoding: chunked\nDate: Sat, 12 Sep 2015 09:07:19 GMT\nServer: Warp/3.1.3\n\n➜  ~  curl -i -XPOST -H \"Content-Type: application/json\" --data '{\"whoo\": \"great horned owl\", \"passwoord\": \"tiger\"}' localhost:8081/api/users/owl-in\nHTTP/1.1 201 Created\nTransfer-Encoding: chunked\nTransfer-Encoding: chunked\nDate: Sat, 12 Sep 2015 09:07:36 GMT\nServer: Warp/3.1.3\nContent-Type: application/json\n\n\"88255ebf-2dca-4638-b037-639fb762f6e0\"\n\n➜  ~  curl -i -XGET -H \"Content-Type: application/json\" -H \"Authorization: 88255ebf-2dca-4638-b037-639fb762f6e0\" localhost:8083/api/albooms/\nHTTP/1.1 200 OK\nTransfer-Encoding: chunked\nDate: Sat, 12 Sep 2015 09:07:48 GMT\nServer: Warp/3.1.3\nContent-Type: application/json\n\n[[{\"image\":\"http://i.imgur.com/PuhhmQi.jpg\",\"description\":\"Scating\"},{\"image\":\"http://i.imgur.com/v5kqUIM.jpg\",\"description\":\"Taking shower\"}],[{\"image\":\"http://i.imgur.com/3hRAGWJ.png\",\"description\":\"About to fly\"},{\"image\":\"http://i.imgur.com/ArZrhR6.jpg\",\"description\":\"Selfie\"}]]\n```\n\nConclusion\n----------\n\nWe just saw how easy it is to write some boilerplate to use The\nMicroservice Architecture, keeping our type-safety for us and our\nfuture team mates happy.\n\nI hope you enjoyed it.\n\nPlease, send your PRs improving both code and tutorial if you feel\nlike doing that.\n\n![LLAP](http://i.imgur.com/OBxk06B.jpg)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk-bx%2Fowlcloud","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fk-bx%2Fowlcloud","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk-bx%2Fowlcloud/lists"}