Giter VIP home page Giter VIP logo

miniblog's Introduction

Client-Server Routing with WebSharper

This article is part of F# Advent. Thanks to Sergey Tihon for organizing!

The main advantage of using a single language for a full-stack project (be it in JavaScript/TypeScript, or better, F# :) ) is to have common abstractions shared between server and client-side code. This makes making updates to full-stack project much safer and quicker. WebSharper's philosophy is to enable full-stack F#/C# projects that take advantage of this.

Today we are looking at how to share a sitemap and router (mapping between URLs and locations in the site) between client and server. We will be able to:

  • Generate links from site locations (Endpoint values)
  • Handle certain link navigations (URL changes) on the client without reloading the page, while reloading from server when we enter another part of the site

So routing duties are split between the server and client, meanwhile both sides are able to link to any page on the site safely. This is a compromise between a many-page site and a single-page application which can be useful where speed of navigation is preferred in some page navigations and full page reload for up-to date information or server-side processing in others.

Our demo project is a rudimentary blog engine, that has a Home page with the full list of articles, Article pages for each entry for reading, and an Edit page for editing a selected article or a creating a new one. The sitemap is represented by this type:

    type EndPoint =
        | [<EndPoint "/">] Home
        | [<EndPoint "/article">] Article of id: int
        | [<EndPoint "/edit">] Edit of id: int

Navigation between Home and individual articles don't reload the page, while entering/exiting Edit pages do.

Splitting between these lines can be centralized with an active pattern:

    let (|ReadPage|EditPage|) e =
        match e with
        | Home
        | Article _ -> ReadPage
        | Edit _ -> EditPage

Setup

As this sample uses some easy persistence by writing/reading .txt files in an /articles folder, there is no online demo.

To check out the sample project, run git clone https://github.com/Jand42/MiniBlog then dotnet run from the MiniBlog folder. Then open http://localhost:5000/

Screenshot of home page

How WebSharper creates routers

If you have and F# union EndPoint type as above, you can create a router instance with let siteRouter = Router.Infer<EndPoint>() that uses the union type's annotations. We can reuse this to guide the both server-side and client-side parsing of URLs and link generation in a uniform way.

Let's look at first what the server's doing with it:

    [<Website>]
    let Main =
        Sitelet.New siteRouter (fun ctx endpoint ->
            match endpoint with
            | ReadPage -> 
                Templating.Main ctx endpoint "MiniBlog" [
                    div [] [client <@ Client.ReadPage endpoint @>]
                ]
            | EditPage -> 
                Templating.Main ctx endpoint "Edit article" [
                    div [] [client <@ Client.EditPage endpoint @>]
                ]
        )

If we encounter a read-only page, we set up the client by passing along our parsed endpoint value, same for an edit page. The client <@ ... @> helper means: this is content to be generated in JavaScript, server just returns a placeholder and browser does the rest. Arguments can be passed along and they are auto-serialized.

We also see we are creating a Sitelet object, which is essentially a router and a rendering function paired together in a type-safe abstraction.

Setting up client-side routing

In client-side code (to be transpiled to JavaScript), this is where the magic happens:

    let ReadPage endpoint =
        let endpointVar = Var.Create endpoint
        
        siteRouter
        |> Router.Filter (
            function
            | Routing.ReadPage -> true
            | _ -> false
        )
        |> Router.InstallInto endpointVar Home

For the ReadPage handler, we only want to handle certain pages, so we filter for it, then install the router as an two-way binding between the URL and a reactive variable.

Then we can tie rendering our page by mapping from this Var, calling the server asynchronously for the content we need in simple records.

This is the part rendering the Home page, containing full list of entries, using the templating engine to define our exact visuals in Html:

        endpointVar.View
        |> Doc.BindView (
            function
            | Home ->
                async {
                    let! articles = Server.GetArticles()
                    return
                        articles 
                        |> Seq.map (fun a ->
                            Templates.MainTemplate.Article()
                                .Title(a.Title)
                                .Text(createContent a.Text)
                                .ReadArticleLink(siteRouter.Link (Article a.Id))
                                .EditArticleLink(siteRouter.Link (Edit a.Id))
                                .Doc()
                        )
                        |> Doc.Concat
                }
                |> Doc.Async

Take a look at the sample project to see how Rpc functions in the Server module work, it's not a robust solution for this demo, just reading/writing .txt files and keeping an in-memory cache of them too.

Note how we can fill in links generated by the router, and we don't have to worry if the link is handled on the client side or it is let to fall through to go to server for a page reload.

Thank you for reading!

Feel free to discuss on Gitter

Happy New Year!

miniblog's People

Contributors

jand42 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.