Code Monkey home page Code Monkey logo

binserve's Introduction

binserve logo

binserve ๐Ÿš€๐Ÿฆ€

A fast static web server with TLS (HTTPS), Routing, Hot Reloading, Caching, Templating, and Security in a single-binary you can set up with zero code.

Built from the ground up for self-hosters with performance, ease of use, and portability in mind. โค๏ธ

version gitHub license twitter share

Table of Contents

Example: Hosting a website produced by a Static Site Generators like Hugo, Zola, Jekyll, Hexo, etc.

Features

  • Fast: Binserve is designed to be performant, this is thanks to Actix-Web - one of the fastest web frameworks out there and DashMap for handling routes and the cache storage. (See Benchmarks)
  • Portability: Binserve is cross-platform and portable to any major operating system, like it can run on your Android phone!
  • Routing: Routing is simply matching a URI path to a file or a directory in a JSON file. (See Configuration)
  • Templating: You can write templates and partials using Handlebars. (See Templating)
  • Hot Reload: You can reload your configuration (routes) and static files with no downtime.
  • Caching: Binserve's performance is achieved due to minimization of Disk I/O operations at runtime (with fast_mem_cache enabled) and serving static files from memory. On the client-side, Cache-Control, Etag, and Last-Modified are utilized.
  • Security: Prevents common attack vectors like Directory Traversal and Symlink Attacks.

๐Ÿ‘‹ Enterprise?

If you're deplyoing to production or expecting high traffic to your server, get binserve+ which has DDoS Protection, Rate Limiting, and Prometheus Metrics for monitoring along with all the above features built-in.

Checkout Binserve Plus!

Read FAQ for more details.

Hello World!

Download the executable for your OS from Releases, then just run it:

mkdir mywebsite/
binserve

On the first run, it will create the configuration file and a starter boilerplate for you to get started.

 _   _
| |_|_|___ ___ ___ ___ _ _ ___
| . | |   |_ -| -_|  _| | | -_|
|___|_|_|_|___|___|_|  \_/|___| 0.2.0

[INFO] Build finished in 295 ฮผs โšก
[SUCCESS] Your server is up and running at 127.0.0.1:1337 ๐Ÿš€

Go to http://127.0.0.0:1337/ and you will be greeted with the index page of Binserve.

Now all you need to do is to edit the binserve.json file. (See Configuration).

Installation

Download the executable from Releases OR Install with cargo:

cargo install --git https://github.com/mufeedvh/binserve.git

Install Rust/Cargo

Build From Source

Prerequisites:

  • Git
  • Rust
  • Cargo (Automatically installed when installing Rust)
  • A C linker (Only for Linux, generally comes pre-installed)
git clone https://github.com/mufeedvh/binserve.git
cd binserve/
RUSTFLAGS="-C target-cpu=native" cargo build --release

The first command clones this repository into your local machine and the last two commands enters the directory and builds the source in release mode.

Configuration

The configuration file is a JSON file called binserve.json that's generated automatically by the executable. Configuring binserve is pretty straight-forward because the configuration fields are self-explanatory:

And all of the values here have secure defaults so you don't have to specify the ones you don't need.

๐Ÿ’ก TIP: Most probably you wouldn't be needing all of the configuration fields, checkout the Static Site Generator example on how to serve a single directory.

{
    "server": {
        "host": "127.0.0.1:1337",
        "tls": {
            "host": "127.0.0.1:443",
            "enable": false,
            "key": "key.pem",
            "cert": "cert.pem"
        }
    },

    "routes": {
        "/": "public/index.html",
        "/usage": "public/usage.hbs",
        "/blog": "public/blog/"
    },

    "static": {
        "directory": "public/assets",
        "served_from": "/assets",
        "error_pages": {
            "404": "public/404.html"
        }
    },

    "template": {
        "partials": {
            "header": "public/header.hbs"
        },
        "variables": {
            "app_name": "Binserve"
        }
    },

    "config": {
        "enable_hot_reload": true,
        "fast_mem_cache": true,
        "enable_cache_control": true,
        "enable_directory_listing": true,
        "minify_html": false,
        "follow_symlinks": false,
        "enable_logging": false
    },

    "insert_headers": {
        "x-greetings": "hellooo!"
    }
}

You can override the configuration with command-line arguments as well:

  • -c/--cert - The path to the TLS certificate for your domain.

  • -k/--key - The path to the TLS key for your domain.

  • -h/--host - The host/domain with the specified port for your webserver to run on.

    • Example: --host 127.0.0.1:1337 OR --host zombo.com

TLS

There is built-in support for TLS:

{
    "server": {
        "host": "127.0.0.1:1337",
        "tls": {
            "host": "127.0.0.1:443",
            "enable": true,
            "key": "key.pem",
            "cert": "cert.pem"
        }
    }
}

The key and certificate can be generated with openssl:

# generate pkcs#10 key+cert (PEM):
$ openssl req -x509 -newkey rsa:4096 -keyout key_pkcs10.pem -out cert.pem -sha256 -days 36

# convert the private key to PKCS#8 (PEM):
$ openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in key_pkcs10.pem -out key.pem

Templating

Binserve uses Handlebars for templating as it's simple and the most commonly known templating engine.

You can register partial templates and template variables like this in the configuration file:

"template": {
    "partials": {
        "header": "public/header.hbs"
    },
    "variables": {
        "app_name": "Binserve"
    }
}

public/header.hbs:

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ app_name }} v0.2.0 Usage</title>
    <link rel="stylesheet" href="assets/css/styles.css">
</head>

And use it like this:

<html>
    {{> header}}
    <body>Hello World!</body>
</html>

Which would render down to:

<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Binserve v0.2.0 Usage</title>
        <link rel="stylesheet" href="assets/css/styles.css">
    </head>
    <body>Hello World!</body>
</html>

Static Site Generators

Every static generator builds your Markdown/Template files into a directory, usually named public/, all you have to do is point that directory as the index route:

{
    "server": {
        "host": "127.0.0.1:1337",
        "tls": {
            "host": "127.0.0.1:443",
            "enable": false,
            "key": "key.pem",
            "cert": "cert.pem"
        }
    },
    "routes": {
        "/": "public/"
    }
}

That's it!

As mentioned previously, you don't have to specify all the fields, secure defaults will be applied! ๐Ÿ™Œ

Pointing directories as routes is an intentional design so that you can host multiple Static Site Generator outputs easily. Let's say you have a portfolio as the homepage and your blog is made with a different template. You can just do:

"routes": {
    "/": "my_zola_portfolio/public/",
    "/blog": "my_hugo_blog/public/"
}

Portability

Binserve is cross-platform which means you can run it on any major operating system / devices. It is low on CPU usage and memory footprint so you can run it on your Raspberry Pi or even your Android Phone:

Caching

With fast_mem_cache enabled, all files are stored in-memory mapped to it's route index and response on initialization and will not perform any disk reads at runtime. It is recommended to keep it enabled even if you have hundreds of files, Binserve automatically sorts large files to be read from disk. Only when you are serving lots of large media content you should disable this mode.

Under the hood, binserve maps the routes and prepares the response with the file's content, mime type, metadata, and the required fields to derive the Etag and Last-Modified tags beforehand and will not perform any of these operations at runtime. Upon changes to any of these files, hot reload is executed in a background thread which updates the program state by making changes to the concurrent hashmap which manages the cache, changes are instant and wouldn't cause any downtime either.

Benchmarks

See BENCHMARKS.md

FAQ

Q: What is binserve+?

Binserve+ is made for websites meant to run in production and handle high amounts of traffic. It comes with DDoS Protection, Rate Limiting, and Prometheus Metrics out-of-the-box.

You get lifetime license + lifetime bug fixes for $24/once.

This exists as a way to support the project, it does not have any license keys or verification system, you get pre-compiled executables for major operating systems and architectures in a ZIP archive. (MIT License)

Get binserve+.

Contribution

Ways to contribute:

  • Suggest a feature
  • Report a bug
  • Fix something and open a pull request
  • Help me document the code
  • Spread the word

License

Licensed under the MIT License, see LICENSE for more information.

Credits

Binserve wouldn't exist without these amazing projects:

  • actix-web - Binserve runs on top of actix-web, the performance wouldn't be achievable without it.
  • dashmap - The in-memory file cache is stored using dashmap for high concurrency reads.
  • ahash - aHash is the hashing algorithm used for the dashmap.
  • compact_str - A memory efficient string type that can store up to 24* bytes on the stack. Route index keys are stored as CompactStrings.
  • handlebars-rust - This library is used for the Handlebars templating and rendering.
  • jwalk - This library helps binserve to index/walk directories quickly in parallel.
  • minify-html-one-pass - Fast HTML minification library, helps to drastically reduce rendering times for reloading/saving hundreds of files.
  • notify - Hot reloading depends on this library to watch for filesystem events and update the program state in realtime.
  • once_cell - A lazy static assignment library, this helps in managing the global program state under an "RwLock" implementation of dashmap.
  • parking_lot - Binserve uses parking_lot's Mutex implementation for the global configuration state.
  • rustls - The TLS implementation used by Binserve is written in pure Rust and this eliminates the need for OpenSSL.
  • serde - The serialization framework used by Binserve for managing the configuration settings, feels like magic and extremely good documentation.

Thank you! โค๏ธ


binserve's People

Contributors

amythicdev avatar ch1nq avatar colemickens avatar kianmeng avatar liushuyu avatar logankaser avatar mufeedvh avatar nicolaiunrein avatar rivy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

binserve's Issues

Windows Version throws 404

Hi,
I have a static web app generated from nuxt.
Problem:
When I press a reload button I get an Error 404. This only happens in binserve windows. With the exact same config and same public folder this error does not happen in linux.
I also tested the static page with caddy and simple-http-server and it worked. Not as fast as with binserve, but it works. ;-)

The page was created by a third party and I'm no html/javascript expert.
The nuxt button component is written like this and vue-router is 3.6.x:

<template>
  <v-btn
    class="reset-button"
    @click="resetState"
    outlined
    elevation="1"
    :ripple="false"
  >
    <v-icon>mdi-refresh</v-icon>Start Fresh
  </v-btn>
</template>

<script>
import { defineComponent } from 'vue';

export default defineComponent({
  name: "Reload",
  setup() {
    const resetState = () => {
      location.reload();
    };

    return {
      resetState,
    };
  },
});
</script>

But since it works on linux and on other web server, I guess this must be some error or something i did not configure right.

binserve.json

{
    "server": {
        "host": "127.0.0.1:3000"
    },

    "routes": {
        "/": "public"
    }, 

    "config": {
        "enable_hot_reload": false,
        "fast_mem_cache": false,
        "enable_cache_control": true,
        "enable_directory_listing": false,
        "minify_html": false,
        "follow_symlinks": false,
        "enable_logging": false
    }
}

I tried several combinations of the config settings. Does not seem to make a difference.
Some clarification about the paths in the docs would be nice, especially for the windows case.

Use actix_web::main for main function

The actix_web's newest stable release re-exports actix_rt::main. So you probably should change your main function to this

#[actix_web::main]
async fn main() -> std::io::Result<()>  {

And append main to the use line of actix_web in the main.rs file.

You should also probably remove actix-rt from the dependencies list in your Cargo.toml because I do not see any other use for it.

I should have filled up a PR but I didn't had the time to fork, change and file it. Also I didn't fully studied your codebase

v0.2.0 Draft

v0.2.0-draft


Screenshot from 2022-03-26 04-42-52


Custom config location

Thanks for the project, it is really awesome!

Right now, the configuration file is hardcoded to be read from the current directory. Sometimes it becomes necessary to run binserve from a directory which does not have a binserve.json. In this case, it would be great if we have the option to specify the location of the configuration file.

Unable to cargo build on MacOS

I'm unable to build the binary on my mac.

Build:

$ cargo build
    Updating crates.io index
 Downloading actix-web v3.0.2
error: unable to get packages from source

Caused by:
  failed to parse manifest at `/Users/xxx/.cargo/registry/src/github.com-1ecc6299db9ec823/actix-web-3.0.2/Cargo.toml`

Caused by:
  feature `rename-dependency` is required

this Cargo does not support nightly features, but if you
switch to nightly channel you can add
`cargo-features = ["rename-dependency"]` to enable this feature

System:

$ uname -a
Darwin xxx 19.6.0 Darwin Kernel Version 19.6.0: Mon Aug 31 22:12:52 PDT 2020; root:xnu-6153.141.2~1/RELEASE_X86_64 x86_64

Cargo version:

$ cargo --version
cargo 1.30.0 (a1a4ad372 2018-11-02)

Emojis are not getting Rendered Properly

Hey,
Unicoded Emojies like ๐Ÿ˜‹ ๐Ÿ“™ are not Getting Rendered Properly.

Eg:
If I edit my "template_variables" in binserve.json to

"template_variables": {
"load_static": "/static/",
"name": "This is test ๐Ÿ˜‹"
}

The Index page get rendered as ๐Ÿ‘‡

SS

And I believe this is something that should be fixed! Right?

OS:Windows 10 64Bit
Binserve used : binserve-v0.1.0-x86_64-pc-windows-gnu.exe

Add some CLI flags for typical config options

E.g., the port, whether to have dir listings, whether to follow symlinks, etc.

Writing a config file is kind of overkill for firing up a throwaway HTTP server.

PS: Adding some screenshots of the directory listings will also help adoption.

Add reverse-proxy functionality

@luciusmagn suggested this feature, Thank You! ๐Ÿ™Œ

i.e. rerouting domains and routes to different ports on the machine

Multiple domains are not handled at the moment, I will probably think about adding this.

Implementation

Here is how I am planning to implement this feature.

The configuration will look like this, just use a hostname:port combination instead of static file route to configure. simple.

"routes": {
    "/": "index.html",
    "/example": "example.html",
    "/blog": "blog.example.com",
    "/staging": "blog.example.com:8080"
},

While checking for routes, it reads the file (NamedFile()) and responds with the corresponding Content-Type at the moment, this feature should require a check for hostname/file validation before the response.

SIDE NOTE: This might be expensive in performance, in-memory/cached handling of the configuration would be a workaround?

Integration with other Rust libraries

I would not like to call this an issue (maybe it is) but rather a question. So you describe in the README on how you can use the template_variables key in the binserve.json file to serve variables. This got me a question about how can someone integrate with other Rust libraries like Diesel so that it can interact with a database or maybe something even simple like getting some information from the URL.

I didn't got any links to Documentation and the README didn't said enough so I filled this issue

Append *.html files with .hbs extension

According to some of the information on handlebars, you should add a .hbs extension. You can replace all ..html with it or add it after the .html. This change can help some IDEs such as VSCode which has both HTML filetype as well as HBS filetype. You should also change the filenames in the setup_static function as well as setup_404 function

Dockerfile

Hi
It would be great if you add a production-ready Dockerfile
Thanks

Configuration file should be taken as an argument

Currently for convenience, a configuration file is generated on the first run and is always consumed from the same working directory as the binary is executed. This implementation was to make project separation easier but comes with an overlooked issue where the executable file could/should be placed outside of a project directory.

This is a tracking issue alongside #32 to implement override for every configuration functions.

Store config in the cache

Currently, the configuration file is saved as an environment variable instead of reading the file on every request so as to improve performance but how about storing that in cache? We need more speed! โšก๐Ÿ”ฅ

I am gonna implement this but I am kinda busy at the moment so if you want to do this, I would really appreciate it ๐Ÿ™Œโฌ‡๏ธ

You wanna do this?

Here's everything you need:

All the functions for configuration are in the config.rs file.

Here is the function that stores the config:

Lines: L15-L22

fn save_config() -> std::io::Result<()> {
    let config_file = File::open(CONFIG_FILE)?;
    let mut buf_reader = BufReader::new(config_file);
    let mut json_string = String::new();
    buf_reader.read_to_string(&mut json_string)?;
    env::set_var("JSON_CONFIG", json_string);
    Ok(())
}

Here is the function that fetches the config:

Lines: L65-L72

pub fn get_config() -> serde_json::Value {
    let bs_config = env::var("JSON_CONFIG").unwrap();

    let json_config: serde_json::Value =
        serde_json::from_str(&bs_config).expect("JSON was not well-formatted");

    json_config
}

Just change these two functions to store the config in the cache and fetch it from the cache respectively! โšก


Panic when a config file is incomplete

When a config file is incomplete binserve should use secure defaults instead of panicing. When a config file is malformed there should be an understandable error message and an exit with a non-zero status code.

Ran in alpine:latest container:

# ./binserve-v0.1.0-x86_64-unknown-linux-musl
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/security.rs:109:47
stack backtrace:
   0:           0x65daaa - std::backtrace_rs::backtrace::libunwind::trace::hf222ece681d618dd
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/../../backtrace/src/backtrace/libunwind.rs:96
   1:           0x65daaa - std::backtrace_rs::backtrace::trace_unsynchronized::h7bfcf0be2fa82989
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/../../backtrace/src/backtrace/mod.rs:66
   2:           0x65daaa - std::sys_common::backtrace::_print_fmt::h236d9b171ad18828
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/sys_common/backtrace.rs:79
   3:           0x65daaa - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h4b36b828e94cdbaa
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/sys_common/backtrace.rs:58
   4:           0x54eb3c - core::fmt::write::h0dd4368b249898df
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/core/src/fmt/mod.rs:1080
   5:           0x65d1b1 - std::io::Write::write_fmt::hc892ee261e6ddf46
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/io/mod.rs:1516
   6:           0x65cb9d - std::sys_common::backtrace::_print::h8b2bc12d86f6d6ce
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/sys_common/backtrace.rs:61
   7:           0x65cb9d - std::sys_common::backtrace::print::ha0d71e055133e020
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/sys_common/backtrace.rs:48
   8:           0x65cb9d - std::panicking::default_hook::{{closure}}::h1db1a3550f89322b
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/panicking.rs:208
   9:           0x65c222 - std::panicking::default_hook::hfe146431cb18e73a
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/panicking.rs:227
  10:           0x65c222 - std::panicking::rust_panic_with_hook::hef9392580f57df9b
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/panicking.rs:577
  11:           0x65bf08 - std::panicking::begin_panic_handler::{{closure}}::hc22f01b65500c5e4
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/panicking.rs:484
  12:           0x65bed4 - std::sys_common::backtrace::__rust_end_short_backtrace::h23c7f1d7574039e8
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/sys_common/backtrace.rs:153
  13:           0x65be8d - rust_begin_unwind
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/std/src/panicking.rs:483
  14:           0x54d360 - core::panicking::panic_fmt::h5f46bd9f58c47694
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/core/src/panicking.rs:85
  15:           0x54d17c - core::panicking::panic::hfc8a9856fb2eca80
                               at /rustc/7f7a1cbfd3b55daee191247770627afab09eece2/library/core/src/panicking.rs:50
  16:           0x4130aa - binserve::binserve_init::hc5b7ef2db614b4b6
  17:           0x522db2 - std::thread::local::LocalKey<T>::with::h16e9249519e7350b
  18:           0x522ab6 - std::thread::local::LocalKey<T>::with::h14c0541cdbdc56bc
  19:           0x414810 - binserve::main::h5a440c368087745b
  20:           0x522943 - std::sys_common::backtrace::__rust_begin_short_backtrace::h413f99a6607c1379
  21:           0x415a58 - main
Panic in Arbiter thread.

Content of the config file:

{}

Support for handlebars partials

It would be great if we could define partials and reference them in our templates. Ideally, I'd like to define a single layout template with a content block which could be filled in by a number of other templates.

I'd be happy to implement this if that would help, but I would like some guidance on how you'd like it integrated if I were to create a PR.

Thanks for the awesome project BTW ๐Ÿ’ฏ

Move the ASCII art to a separate file

I was inspecting your sources, The ASCII art didn't looked good in the middle of the source code. It would be better if you move it to a separate file or maybe make it a const. Also the line that tells about the host and port could be put along with the ASCII art. You should use the include_str! macro to include it into the source code during compilation and use it in the println! also specifying the host and port

Writing API docs

As exploring through your project, I found that it does not have API documentation. It is very necessary, especially for new contributors that they are provided with it so it is easier for them. It is also helpful for people like us who sometimes need reference to a function or a struct

It seems you haven't written binserve simultaneously writing code and docs, feel free to tell me if you need help while writing the docs

Simpler run configurations without config file + tls instructions

binserve seems really great but I really wish it were a bit easier to use:

  1. It would be really great to be able to just do binserver --auto-self-signed-tls --dir ./www and have it generate a self-signed key/cert and serve from the directory.
  2. I have no idea how to generate certs/keys for this. I have my openssl one-liner and if I try to use that I get:
[cole@slynux:~/code/webrtcsink]$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365                                                                        
redacted....


[cole@slynux:~/code/webrtcsink]$ binserve
[INFO] Build finished in 485 ฮผs โšก
[INFO] Enabled TLS (HTTPS) ๐Ÿ”’
[SUCCESS] Your server is up and running at 0.0.0.0:8001 ๐Ÿš€
[ERROR] Could not locate PKCS 8 private keys.

Write a dedicated documentation page (`DOCUMENTATION.md`)

Every function in the configuration file should be documented in detail as to what it does and how it works, currently it's just a dump of the JSON file and doesn't go into detail on any of the options.

A file called DOCUMENTATION.md should be created for this outside of the README.

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.