5
comments
on 2/13/2015 3:43 AM

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!

.Comment
Hide this comment

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?

By on 3/3/2015 8:14 AM ()Reply
Hide this comment

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.

By on 3/3/2015 11:11 AM ()Reply
Hide this comment

Thanks a lot! Works like a charm

By on 3/5/2015 9:42 AM ()Reply
Hide this comment

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

By on 3/30/2015 1:47 AM ()Reply
Hide this comment

Indeed, thanks! I fixed the link.

By on 3/30/2015 1:58 AM ()Reply
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper

Logging in...