Search code examples
haskellroutesweb-frameworks

Haskell Web frameworks that discover endpoints?


Is there a haskell web framework or method to make end rest api endpoint methods discoverable, instead of having to go back to append the new endpoint function to a main method?

Explanation:

I'm evaluating haskell web frameworks and something I see common in all of them is that there is one place to define your endpoints.

Meaning, each time I create a new endpoint, I will have to make the function and then enter the endpoint in the main function.

I'll give some examples.

Spock

type Api = SpockM () () () ()

type ApiAction a = SpockAction () () () a

main :: IO ()
main = do
  spockCfg <- defaultSpockCfg () PCNoDatabase ()
  runSpock 8080 (spock spockCfg app)

app :: Api
app = do
  get "people" $ do
    json $ Person { name = "Fry", age = 25 }

For a new endpoint, I will need to modify the app function.

Servant

type UserAPI2 = "users" :> Get '[JSON] [User]
           :<|> "albert" :> Get '[JSON] User
           :<|> "isaac" :> Get '[JSON] User

isaac :: User
isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)

albert :: User
albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)

users2 :: [User]
users2 = [isaac, albert]

server2 :: Server UserAPI2
server2 = return users2
     :<|> return albert
     :<|> return isaac

For each new endpoint, I will need to append to the server2 function.

Snap

appInit :: SnapletInit App App
appInit = makeSnaplet "myapp" "My example application" Nothing $ do
    hs <- nestSnaplet "heist" heist $ heistInit "templates"
    fs <- nestSnaplet "foo" foo $ fooInit
    bs <- nestSnaplet "" bar $ nameSnaplet "newname" $ barInit foo
    addRoutes [ ("hello", writeText "hello world")
              , ("fooname", with foo namePage)
              , ("barname", with bar namePage)
              , ("company", companyHandler)
              ]
    wrapSite (<|> heistServe)
    ref <- liftIO $ newIORef "fooCorp"
    return $ App hs fs bs ref

For each new endpoint I want to add, I will need to append to the addRoutes function.

In all these examples and every other framework I look at, if I want to add a new endpoint, I always have to come back to a function in the main file to add the new route.

I was hoping there would be something out there, that I can just write my new endpoint function within a new file and it would be discovered by the web framework, so I don't have to keep adding to an existing function.

In .net Web Api, this would look like this.

One place to define the default routing.

routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

A new class and file added to the project. The framework would discover this method as an endpoint at runtime and add it. I won't have to go back to a main function to append this endpoint.

[Route("api/books")]
[HttpPost]
public HttpResponseMessage CreateBook(Book book) { ... }

Solution

  • You can get pretty close to what you are expecting with the Yesod framework provided that you are using the scaffolded site it provides. The flow will look like this:

    1) Add new route in the routes file

    /myRoute MyRouteR GET
    

    2) Define a handler for it in the existing Handler module under the Handler directory:

    getMyRouteR :: Handler Html
    getMyRouteR = return $(shamletFile "templates/myRoute.shamlet")
    

    And that's the end of the flow.