WebSharper
By Loïc Denuzière on Friday, February 13, 2015 — 5 comments

Upcoming in WebSharper 3.0: serving REST APIs, easy as pie!Core team

WebSharper Sitelets are a wonderful way to build websites quickly and safely. They can automatically manage your URLs in a type-safe way, and generate HTML markup using simple F# combinators. In WebSharper 3.0, we are extending Sitelets with the capability to create REST APIs with unrivaled simplicity.

Requests

Just like you can currently type these lines in F#:

1
2
3
type Action =
	| [<CompiledName "listing">] Listing of pagenum: int
    | [<CompiledName "article">] Article of id: int * slug: string

to tell WebSharper that your site will be served on these URLs:

1
2
/listing/1
/article/135/upcoming-in-websharper-30

You can now also specify all the information necessary to serve a web API on a given HTTP method and with the given JSON body, simply by using a couple attributes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Action =
	| [<Method "GET"; CompiledName "article">]
    	GetArticle of id: int
    | [<Method "POST"; CompiledName "article"; Json "data">]
    	PostArticle of data: ArticleData

and ArticleData =
	{
    	author: string
        title: string
        tags: Set<string>
        summary: option<string>
        body: string
    }

The following requests are now accepted:

1
GET /article/135

1
2
3
4
5
6
7
8
9
POST /article

{
  "author": "loic.denuziere",
  "title": "Upcoming in WebSharper 3.0: serving REST APIs, easy as pie!",
  "tags": ["websharper", "fsharp"],
  "summary": "WebSharper 3.0 is coming with an (...)",
  "body": "WebSharper Sitelets are a wonderful way to (...)"
}

The JSON serialization provides all the niceties possible to be friendly with F# types:

  • F# records are represented as JSON objects;
  • list<'T>, 'T[] and Set<'T> are representad as JSON arrays;
  • Map<string, 'T> is represented as a flat JSON object;
  • F# fields of type option<'T> are represented as a JSON field that is present if Some or absent if None;
  • F# unions are represented as JSON objects using the union field names, and a separate named field to indicate the union case (the name of this field is specified in an attribute).

Responses

Of course, a REST API is not just parsing requests, but also writing responses. For this too, WebSharper 3.0 has you covered. A new function Content.JsonContent allows you to serve any F# value as JSON with zero hassle:

1
2
3
4
5
6
7
let mySite = Sitelet.Infer <| function
	| GetArticle id ->
    	Content.JsonContent <| fun ctx ->
        	{ author = "loic.denuziere"; (* ... *) }
    | PostArticle articleData ->
    	Content.JsonContent <| fun ctx ->
        	SaveArticle articleData

Want to see a full example? How about a full CRUD API serving an in-memory database of people information, with all interactions perfectly type-safe, in less than fifty lines?

Look for this new WebSharper 3.0 pre-release on NuGet early next week, or build it right now from source!

  • mikael.bonsdorff@gmail.com

    Hi! This looks really interesting feature for WebSharper. I'm quite newbie with WebApi and WebSharper, so one question: any idea how to allow CORS with WebSharper REST API?

    • loic.denuziere

      CORS support involves two steps: the preflight OPTIONS request (if you use other methods than GET and POST), and the main request.

      Here is an example to handle both parts:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      
      // HELPER FUNCTIONS
      
      // Adds the Access-Control-Allow-Origin header if the request has an Origin header.
      let AddAllowOrigin (content: Content<'T>) : Content<'T> =
          Content.CustomContentAsync <| fun ctx -> async {
              let! response = Content.ToResponseAsync content ctx
              let headers =
                  ctx.Request.Headers
                  |> Seq.tryFind (fun h -> h.Name = "Origin")
                  |> Option.map (fun origin ->
                      Http.Header.Custom "Access-Control-Allow-Origin" origin.Value)
                  |> Option.toList
              return { response with Headers = Seq.append headers response.Headers }
          }
      
      // Creates a preflight response that allows the given methods.
      let Preflight (methods: seq<string>) : Content<'T> =
          Content.CustomContent <| fun ctx ->
              let methods = String.concat ", " methods
              let headers = [ Http.Header.Custom "Access-Control-Allow-Methods" methods ]
              { Status = Http.Status.Ok; Headers = headers; WriteBody = ignore }
          |> AddAllowOrigin
      
      
      // APPLICATION CODE
      
      type MyAction =
          // preflight request
          | [<Method "OPTIONS"; CompiledName "my-api">] MyApiOptions
          // main request
          | [<Method "PUT"    ; CompiledName "my-api">] MyApiPut of SomeArgs
      
      let mySite = Sitelet.Infer <| function
          // Respond to the preflight request
          | MyApiOptions -> Preflight ["PUT"]
          // Respond to the actual request
          | MyApiPut args ->
              Content.JsonContent <| fun ctx ->
                  () // Generate your body here...
              |> AddAllowOrigin

      I hope this helps.

      • mikael.bonsdorff@gmail.com

        Thanks a lot! Works like a charm

  • mikkom

    Looks great! However, the link to the full example code does not seem to work.