Web API 

in a F#-way

@nakamura_to

Introduction

twitter

works
  • Doma ... Java OR Mapper
  • Soma  ... .NET OR Mapper developed in F#
  • Tranq ... F# DB Access Library

F# and ASP.NET Web API

F# is powerful.
ASP .NET Web API is nice.
But combination of them is not so good.

  • mutable objects
  • null
  • customization by attribute and inheritance



Enku

Enku - F# Lightweight Web API Framework

  • built on top of ASP.NET Web API
  • functions instead of attributes
  • composition instead of inheritance
  • options instead of nulls
  • async computation expression insteadof Task
  • validation

Enku Example

http://hostname/example
route "example" <| fun _ -> 
  [ 
    get, fun req -> async {
      return Response.Ok "hello" }

    post, fun req -> async {
      let! content = Request.asyncReadAsString req
      return Response.Ok content }

    any, fun req -> async {
      return Response.NotFound "not found" }
  ], 
  fun req e -> Response.InternalServerError e

Request and Response

http://hostname/example/123?name=hoge&age=30
route "example/{?id}" <| fun _ -> 
  [ 
    get, fun req -> async {
      let id: string option = Request.getRouteValue "id" req
      let routeValues: Map<string, string> = Request.getRouteValueMap req
      let name: string option = Request.getQueryString "name" req
      let queryStrings: Map<string, string> = Request.getQueryStringMap req
      return Response.BadRequest "" }

    post, fun req -> async {
      let! (content1: string) = Request.asyncReadAsString req
      let! (content2: Stream) = Request.asyncReadAsStream req
      // Content-Type: application/x-www-form-urlencoded
      let! (content3: Map<string, string list>) = Request.asyncReadAsForm req
      return Response.``200`` "" }
  ], 
  fun req e -> Response.InternalServerError e

Request Header and

Response Header

route "example" <| fun _ ->
  [ 
    get, fun req -> async {
      let host = RequestHeader.Host req
      if Option.isSome host then
        return Response.Ok host.Value 
      else
        return Response.Redirect ""
          |> Response.appendHeaders 
            [ ResponseHeader.Location <| Uri "http://www.google.co.jp/"]}
  ], 
  fun req e -> Response.InternalServerError e

Constraint Composition

let exists value req = Option.isSome value

route "example/{?id}" <| fun req -> 
  let id = Request.getRouteValue "id" req
  [ 
    get <&> exists id, fun req -> async {
      return Response.Ok "hello" }

    put <|> post, fun req -> async {
      let! content = Request.asyncReadAsString req
      return Response.Ok content }
  ], 
  fun req e -> Response.InternalServerError e

Interceptor


let log = Around(fun req action -> async {
  printfn "BEFORE"
  try
    return! action req
  finally
    printfn "AFTER" })

route "example" <| fun _ ->
  Advice.around [log] <|
  [ 
    get, fun req -> async {
      printfn "INVOKED"
      return Response.Ok "hello" }
  ], 
  fun req e -> Response.InternalServerError e
output

BEFORE
INVOKED
AFTER

Validation


module V = Validator

route "example" <| fun _ -> 
  [ 
    get, fun req -> async {
      let qs: Map<string, string> = Request.getQueryStringMap req
      let vc = ValidationContext()
      let name:string option = vc.Eval(qs,"name",V.required)
      let age:int option = vc.Eval(qs,"age",V.int <+> V.range 0 21 <+> V.required)
      match vc.Errors with
      | [] -> ()
      | h :: _ -> Response.BadRequest h |> Routing.exit
      return Response.Ok "" }
  ],
  fun req e -> Response.InternalServerError e

Summary

Enku encourages you to build Web API in a F#-way.
It's easy to customize Enku's default behaviors, because function compositions are available.

Thank you.

Q and A

web api in a fsharp way

By nakamura_to

web api in a fsharp way

  • 3,550