App - Redis

The Redis HTML and Redis Vue Apps below demonstrates the versatility of Sharp Apps showcasing the same App implemented in both server-generated HTML and a Vue SPA App.

Redis HTML

You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):

app://redis-html

Or via command-line:

$ app open redis-html

Cross platform (Default Browser):

$ x open redis-html

For the Redis Browser Web App, we wanted to implement an App that was an ideal candidate for a Single Page App but constrain ourselves to do all HTML rendering on the server and have each interaction request a full-page reload to see how a traditional server-generated Web App feels like with the performance of .NET Core 3.1 and #Script. We're pleasantly surprised with the result as when the App is run locally the responsiveness is effectively indistinguishable from an Ajax App. When hosted on the Internet there is a sub-second delay which causes a noticeable flicker but it still retains a pleasant UX that's faster than most websites.

The benefits of a traditional website is that it doesn't break the web where the back button and deep linking work without effort and you get to avoid the complexity train of adopting a premier JavaScript SPA Framework's configuration, dependencies, workflow and build system which has become overkill for small projects.

Redis HTML WebApp Screenshot

We've had a sordid history developing Redis UI's which we're built using the popular JavaScript frameworks that appeared dominant at the time but have since seen their ecosystem decline, starting with the Redis Admin UI (src) built using Google's Closure Library that as it works different to everything else needed a complete rewrite when creating redisreact.servicestack.net (src) using the hot new React framework, unfortunately it uses React's old deprecated ES5 syntax and Reflux which is sufficiently different from our current recommended TypeScript + React + Redux + WebPack JavaScript SPA Stack, that is going to require a significant refactor to adopt our preferred SPA tech stack.

Beautiful, succinct, declarative code

The nice thing about generating HTML is that it's the one true constant in Web development that will always be there. The entire functionality for the Redis Web App is contained in a single /redis-html/app/index.html which includes all Template and JavaScript Source Code in < 200 lines which also includes all as server logic as it doesn't rely on any back-end Services and just uses the Redis Scripts to interface with Redis directly. The source code also serves as a good demonstration of the declarative coding style that #Script encourages that in addition to being highly-readable requires orders of magnitude less code than our previous Redis JavaScript SPA's with a comparable feature-set.

Having a much smaller code-base makes it much easier to maintain and enhance whilst being less susceptible to becoming obsolete by the next new JavaScript framework as it would only require rewriting 75 lines of JavaScript instead of the complete rewrite that would be required to convert the existing JavaScript Apps to a use different JavaScript fx.

app.settings

The app.settings for Redis is similar to Web App Starter above except it adds a redis.connection to configure a RedisManagerPool at the connection string provided as well as Redis Scripts to give the scripts access to the Redis instance.

debug true
name Redis Web App
redis.connection localhost:6379

Redis Vue

You can run this Gist Desktop App via URL Scheme from (Windows Desktop App):

app://redis

Or via command-line:

$ app open redis

Cross platform (Default Browser):

$ x open redis

Whilst the above server-generated HTML Redis UI shows how you can easily develop traditional Web Apps using #Script, we've also rewritten the Redis UI as a Single Page App which is the more suitable choice for an App like this as it provides a more optimal and responsive UX by only loading the HTML page once on Startup then utilizes Ajax to only download and update the incremental parts of the App's UI that needs changing.

Instead of using jQuery and server-side HTML this version has been rewritten to use Vue where the UI has been extracted into isolated Vue components utilizing Vue X-Templates to render the App on the client where all Redis Vue's functionality is contained within the Redis/app/index.html page.

Redis Vue WebApp Screenshot

Simple Vue App

#Script also provides a great development experience for Single Page Apps which for the most part gets out of your way letting you develop the Single Page App as if it were a static .html file, but also benefits from the flexibility of a dynamic web page when needed.

The containing _layout.html page can be separated from the index.html page that contains the App's functionality, where it's able to extract the title of the page and embed it in the HTML <head/> as well as embed the page's <script /> in its optimal location at the bottom of the HTML <body/>, after the page's blocking script dependencies:

<html>
<head>
<title>{{ title ?? 'Redis Vue SharpApp' }}</title>
<i hidden>{{ '/js/hot-loader.js' |> ifDebugIncludeScript }}</i>

...
<link rel="stylesheet" href="../assets/css/bootstrap.css">
<link rel="stylesheet" href="../assets/css/default.css">
</head>
<body>
    <h2 id="title"><a href="/"><img src="/assets/img/redis-logo.png" /> {{ title }}</a></h2>

    {{ page }}

    <script src="../assets/js/html-utils.js"></script>
    <script src="../assets/js/vue{{ '.min' |> if(!debug) }}.js"></script>
    <script src="../assets/js/axios.min.js"></script>
    
    {{ scripts |> raw }} 
</body>
</html>

Redis Vue avoids the complexity of adopting a npm build system by referencing Vue libraries as a simple script include:

<script src="../assets/js/vue{{ '.min' |> if(!debug) }}.js">

Where it uses the more verbose and developer-friendly vue.js during development whilst using the production optimized vue.min.js for deployments. So despite avoiding the complexity tax of an npm-based build system it still gets some of its benefits like conditional deployments and effortless hot reloading.

Server Pages

Whilst most of index.html is a static Vue app, #Script is leveraged to generate the body of the <redis-info/> Component on the initial home page render:

<script type="text/x-template" id="redis-info-template">
<div id="redis-info">
  <table class="table table-striped" style="width:450px">
    <tbody>
    {{#each toList(redisInfo) }}
        <tr>
          <th>{{ it.Key |> replace('_',' ') }}</th>
          <td title="{{it.Value}}">{{ it.Value |> substringWithEllipsis(32) }}</td>
        </tr>
    {{/each}}
    </tbody>
  </table>
</div>
</script>

This technique increases the time to first paint by being able to render the initial Vue page without waiting for an Ajax call response whilst benefiting from improved SEO from server-generated HTML.

Server Handling

Another area #Script is used is to handle the HTTP POST where it calls the redisChangeConnection filter to change the current Redis connection before rendering the connection-info.html partial with the current connection info:

<script type="text/x-template" id="connection-template">
<div id="connection-info" class="container">
    {{ continueExecutingFiltersOnError }}
    {{#if Request.Verb == "POST" }}
        {{ { host, port, db, password } |> withoutEmptyValues |> redisChangeConnection |> end }}
        {{#if lastErrorMessage }}
            <div class="alert alert-danger">{{lastErrorMessage}}</div>
        {{else}}
            <div class="alert alert-success">Connection Changed</div>
        {{/if}}
    {{/if}}
</div>
</script>

Vue Ajax Server APIs

All other Server functionality is invoked by Vue using Ajax to call one of the Ajax APIs below implemented as API Pages:

search.html

Called when searching for Redis keys where the query is forwarded to the redisSearchKeys filter:

{{ limit ?? 100  |> to => limit }}

{{ `${q ?? ''}*` |> redisSearchKeys({ limit }) 
                 |> return }}
call.html

Called to execute an arbitrary Redis command on the connected instance, with the response from Redis is returned as a plain-text HTTP Response:

{{ { command } |> ensureAllArgsNotEmpty }}

{{ ['flush','monitor','brpop','blpop'] |> any => contains(lower(command), it)
   |> to => illegalCommand }}

{{ illegalCommand ? throwArgumentException('Command is not allowed.') : null }}

{{ command  |> redisCall |> to => contents }}

{{ contents |> return({ 'Content-Type': 'text/plain' }) }}

The benefits of using API Pages instead of a normal C# Service is being able to retain Web App's productive development workflow where the entire Redis Vue App is built without requiring any compilation.

Deep linking and full page reloads

The Redis Vue Single Page App also takes advantage of HTML5's history.pushState API to enable deep-linking and back-button support where most UI state changes is captured on the query string and used to initialize the Vue state on page navigation or full-page reloads where it provides transparent navigation and back-button support that functions like a traditional Web App but with the instant performance of a Single Page App.

made with by ServiceStack