Giter Site home page Giter Site logo

middie's Introduction

@fastify/middie

CI NPM version js-standard-style

@fastify/middie is the plugin that adds middleware support on steroids to Fastify.

The syntax style is the same as express/connect. Does not support the full syntax middleware(err, req, res, next), because error handling is done inside Fastify.

Install

npm i @fastify/middie

Usage

Register the plugin and start using your middleware.

const Fastify = require('fastify')

async function build () {
  const fastify = Fastify()
  await fastify.register(require('@fastify/middie'), {
    hook: 'onRequest' // default
  })
  // do you know we also have cors support?
  // https://github.com/fastify/fastify-cors
  fastify.use(require('cors')())
  return fastify
}

build()
  .then(fastify => fastify.listen({ port: 3000 }))
  .catch(console.log)

Encapsulation support

The encapsulation works as usual with Fastify, you can register the plugin in a subsystem and your code will work only inside there, or you can declare the middie plugin top level and register a middleware in a nested plugin, and the middleware will be executed only for the nested routes of the specific plugin.

Register the plugin in its own subsystem:

const fastify = require('fastify')()

fastify.register(subsystem)

async function subsystem (fastify, opts) {
  await fastify.register(require('@fastify/middie'))
  fastify.use(require('cors')())
}

Register a middleware in a specific plugin:

const fastify = require('fastify')()

fastify
  .register(require('@fastify/middie'))
  .register(subsystem)

async function subsystem (fastify, opts) {
  fastify.use(require('cors')())
}

Hooks and middleware

Every registered middleware will be run during the onRequest hook phase, so the registration order is important. Take a look at the Lifecycle documentation page to understand better how every request is executed.

const fastify = require('fastify')()

fastify
  .register(require('@fastify/middie'))
  .register(subsystem)

async function subsystem (fastify, opts) {
  fastify.addHook('onRequest', async (req, reply) => {
    console.log('first')
  })

  fastify.use((req, res, next) => {
    console.log('second')
    next()
  })

  fastify.addHook('onRequest', async (req, reply) => {
    console.log('third')
  })
}

It is possible to change the Fastify hook that the middleware will be attached to. Supported lifecycle hooks are:

  • onRequest
  • preParsing
  • preValidation
  • preHandler
  • preSerialization
  • onSend
  • onResponse
  • onError
  • onTimeout

To change the hook, pass a hook option like so:

Note you can access req.body from the preParsing, onError, preSerialization and onSend lifecycle steps. Take a look at the Lifecycle documentation page to see the order of the steps.

const fastify = require('fastify')()

fastify
  .register(require('@fastify/middie'), { hook: 'preHandler' })
  .register(subsystem)

async function subsystem (fastify, opts) {
  fastify.addHook('onRequest', async (req, reply) => {
    console.log('first')
  })

  fastify.use((req, res, next) => {
    console.log('third')
    next()
  })

  fastify.addHook('onRequest', async (req, reply) => {
    console.log('second')
  })

  fastify.addHook('preHandler', async (req, reply) => {
    console.log('fourth')
  })
}

Restrict middleware execution to a certain path(s)

If you need to run a middleware only under certain path(s), just pass the path as first parameter to use and you are done!

const fastify = require('fastify')()
const path = require('node:path')
const serveStatic = require('serve-static')

fastify
  .register(require('@fastify/middie'))
  .register(subsystem)

async function subsystem (fastify, opts) {
  // Single path
  fastify.use('/css', serveStatic(path.join(__dirname, '/assets')))

  // Wildcard path
  fastify.use('/css/*', serveStatic(path.join(__dirname, '/assets')))

  // Multiple paths
  fastify.use(['/css', '/js'], serveStatic(path.join(__dirname, '/assets')))
}

⚠️ potential ReDoS attacks

Middie use path-to-regexp to convert paths to regular expressions. This might cause potential ReDoS attacks in your applications if certain patterns are used. Use it with care.

Middie Engine

You can also use the engine itself without the Fastify plugin system.

Usage

const Middie = require('@fastify/middie/engine')
const http = require('node:http')
const helmet = require('helmet')
const cors = require('cors')

const middie = Middie(_runMiddlewares)
middie.use(helmet())
middie.use(cors())

http
  .createServer(function handler (req, res) {
    middie.run(req, res)
  })
  .listen(3000)

function _runMiddlewares (err, req, res) {
  if (err) {
    console.log(err)
    res.end(err)
    return
  }

  // => routing function
}

Keep the context

If you need it you can also keep the context of the calling function by calling run with run(req, res, this), in this way you can avoid closures allocation.

http
  .createServer(function handler (req, res) {
    middie.run(req, res, { context: 'object' })
  })
  .listen(3000)

function _runMiddlewares (err, req, res, ctx) {
  if (err) {
    console.log(err)
    res.end(err)
    return
  }
  console.log(ctx)
}

Restrict middleware execution to a certain path(s)

If you need to run a middleware only under certains path(s), just pass the path as first parameter to use and you are done!

Note that this does support routes with parameters, e.g. /user/:id/comments, but all the matched parameters will be discarded

// Single path
middie.use('/public', staticFiles('/assets'))

// Multiple middleware
middie.use('/public', [cors(), staticFiles('/assets')])

// Multiple paths
middie.use(['/public', '/dist'], staticFiles('/assets'))

// Multiple paths and multiple middleware
middie.use(['/public', '/dist'], [cors(), staticFiles('/assets')])

To guarantee compatibility with Express, adding a prefix uses path-to-regexp to compute a RegExp, which is then used to math every request: it is significantly slower.

TypeScript support

To use this module with TypeScript, make sure to install @types/connect.

Middleware alternatives

Fastify offers some alternatives to the most commonly used Express middleware:

Express Middleware Fastify Plugin
helmet fastify-helmet
cors fastify-cors
serve-static fastify-static

Acknowledgements

This project is kindly sponsored by:

Past sponsors:

License

Licensed under MIT.

middie's People

Contributors

admataz avatar allevo avatar bcomnes avatar billouboq avatar cemremengu avatar chasingsublimity avatar delvedor avatar dependabot-preview[bot] avatar dependabot[bot] avatar domsen123 avatar dr3 avatar eomm avatar fdawgs avatar greenkeeper[bot] avatar jkyberneees avatar kailasmahavarkar avatar kibertoad avatar mcollina avatar nwoltman avatar rafaelgss avatar simenb avatar tirke avatar trygve-lie avatar uzlopak avatar zekth avatar zhaow-de 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  avatar  avatar  avatar  avatar  avatar  avatar

middie's Issues

"next is not a function" using hook preParsing

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

Latest

Plugin version

latest

Node.js version

18

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

20.04

Description

Using option on register middie plugin { hook: 'preParsing' }, and in the middleware calling, next function, this emit a error next is not a function

Steps to Reproduce

Configure the middleware with option { hook: 'preParsing' }, and call the next function inside middleware

Expected Behavior

Not emit errors

Not working without await

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.10.0

Plugin version

8.0.0

Node.js version

19

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

12.6

Description

import Fastify from "fastify";
import FastifyMiddie from "@fastify/middie";

NOT Working

  const app = Fastify();
  app.register(FastifyMiddie);

  app.use() // .use - undefined

Working

  const app = Fastify();
  await app.register(FastifyMiddie);

  app.use()

Steps to Reproduce

import Fastify from "fastify";
import FastifyMiddie from "@fastify/middie";

const app = Fastify();
await app.register(FastifyMiddie);

app.use()

Expected Behavior

plugin and .use available

Process crashes with TypeError: Cannot read properties of null (reading 'originalUrl')

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.2.0

Plugin version

8.0.0

Node.js version

18.5.0

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

13.1

Description

The operating system is FreeBSD. Every once in a while, I get this error and the process crashes:

TypeError: Cannot read properties of null (reading 'originalUrl')
    at Holder.done (/usr/local/www/cinemaseats/node_modules/@fastify/middie/engine.js:78:21)
    at Domain.emit (node:events:537:28)
    at Domain.emit (node:domain:482:12)
    at emit (node:internal/process/promises:147:33)
    at processPromiseRejections (node:internal/process/promises:283:27)
    at process.processTicksAndRejections (node:internal/process/task_queues:96:32)

Steps to Reproduce

I'm only using the Sentry middleware with middie:

await fastify.register(middie);
fastify.use(Sentry.Handlers.requestHandler());

Expected Behavior

No response

Middleware executed on subroutes

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.23.0

Plugin version

5.3.0

Node.js version

16.13.0

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

Ubuntu 18.04

Description

The current configuration used for pathToRegexp causes subroutes to be matched.

Current configuration:

middie/engine.js

Lines 23 to 26 in 33065d3

regexp = pathToRegexp(sanitizePrefixUrl(url), [], {
end: false,
strict: false
})

I'm running into this issue when using Nestjs + Fastify where a controller has middleware applied (using .apply(RootMiddleware).forRoutes(RootController)) and defines a method with one of the following: @Get(), @Get('/'), or @Get(''). Requesting any route not defined in the RootController will cause the RootMiddleware to be executed.

Steps to Reproduce

  1. Register middleware on /
  2. Request /contact
  3. The middleware will execute

Expected Behavior

The middleware registered to '/' should execute on:

  • /
  • /?hello
  • /#hello
  • /?hello#hello

but not execute on:

  • /test
  • /contact

literally everyone will read the name of this package as `middle`

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3

Plugin version

No response

Node.js version

any

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

any

Description

i mean really the first interaction is gonna be "the package cant be found" and will leave people annoyed.

Make it loud and clear that the name of the package is with an I as in Icicle or make a middle package with a postinstall hook that's super loud "THIS IS THE WRONG PACKAGE< YOU PROBABLY MEANT MIDDIE` or something please...

Steps to Reproduce

yarn add @fastify/middle

Expected Behavior

No response

Routes using middie middleware inside fastify plugin do not get registered

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.24.0

Plugin version

5.3.0

Node.js version

14.18.1

Operating system

Windows

Operating system version (i.e. 20.04, 11.3, 10)

11

Description

If you define middleware with a route inside a fastify plugin, the route will not resolve and result in a 404 error.

Steps to Reproduce

You can find a fully working example in this Codesandbox: https://codesandbox.io/s/middie-middleware-inside-fastify-plugin-9nt6e

These are the minimal steps you have to follow:

  1. Register the middie plugin.

    await fastify.register(middiePlugin);
  2. Create another js file with a custom plugin that only registers a certain middleware with a path

    await fastify.use("/demo", (_req, res) => {
      res.write("demo plugin");
      res.end();
    });
  3. Use the middleware in your main js file with a path prefix

    await fastify.register(plugin, { prefix: "plugin" });
  4. Try to navigate to /plugin/demo, this will result in a 404.

Expected Behavior

In the example, when you go to /plugin/demo, you should see demo plugin with status code 200 as result, instead we get a 404.

All the other routes in the example do resolve correctly.

Error: fastify-plugin: middie - expected '>=3.0.0' fastify version, '2.12.1' is installed

🐛 Bug Report

A clear and concise description of what the bug is.

To Reproduce

Steps to reproduce the behavior:

yarn add middie

const fastify = require('fastify')({
  logger: true
})

fastify.register(require('middie'))
    throw new Error(`fastify-plugin: ${pluginName} - expected '${version}' fastify version, '${fastifyVersion}' is installed`)
    ^

Error: fastify-plugin: middie - expected '>=3.0.0' fastify version, '2.12.1' is installed
    at checkVersion (E:\projects\karafs\diet-server-v2\node_modules\middie\node_modules\fastify-plugin\plugin.js:67:11)
    at plugin (E:\projects\karafs\diet-server-v2\node_modules\middie\node_modules\fastify-plugin\plugin.js:36:5)
    at Object.<anonymous> (E:\projects\karafs\diet-server-v2\node_modules\middie\index.js:67:18)
    at Module._compile (internal/modules/cjs/loader.js:1157:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1177:10)
    at Module.load (internal/modules/cjs/loader.js:1001:32)
    at Function.Module._load (internal/modules/cjs/loader.js:900:14)
    at Module.require (internal/modules/cjs/loader.js:1043:19)
    at require (internal/modules/cjs/helpers.js:77:18)
    at Object.<anonymous> (E:\projects\karafs\diet-server-v2\app.js:14:18)
    at Module._compile (internal/modules/cjs/loader.js:1157:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1177:10)
    at Module.load (internal/modules/cjs/loader.js:1001:32)
    at Function.Module._load (internal/modules/cjs/loader.js:900:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
    at internal/main/run_main_module.js:18:47```

## Expected behavior

A clear and concise description of what you expected to happen.

```js
// Paste the expected results here

Your Environment

  • node version: 10, 12, 13
  • fastify version: >=2.0.0
  • os: Mac, Windows, Linux
  • any other relevant information

Add option to pass full fastify request/reply to middleware

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

🚀 Feature Proposal

Some applications would like access to the full fastify request object in middleware. Unfortunately, middie only sends the raw request object.

I propose that an optional parameter be added to change this behavior. The property would tell middie to pass the full fastify request/reply object instead.

This should be non-breaking as it only affects people who enable it.

Motivation

I'm using NestJS with the fastify module and it's not ideal having to do .raw on everything to get values set in middleware.

For a few months now I've been using a patch I wrote to pass it through to the middleware. However, this isn't a permanent solution.

Example

Something like

fastify.register(require('@fastify/middie'), { fullRequest: true })

Add exports field, for @fastify/middie/engine

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

🚀 Feature Proposal

I'm unable to use the /engine in projects using ESM and throws this error:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/tmp/fastify-next-auth/node_modules/@fastify/middie/engine' imported from //tmp/fastify-next-auth/dist/index.mjs

I made it work when I manually changed @fastify/middie package.json by adding exports field:

"exports": {
  ".": {
    "require": "./index.js"
  },
  "./engine": {
    "require": "./lib/engine.js",
    "import": "./lib/engine.js"
  }
},

Would you accept a PR or do you have a better solution? Thank you!

Motivation

Authored

Example

import Middie from '@fastify/middie/engine'

No response

Routes format limitations

Hi guys, I really like the Fastify project and I hope to find the time to try it out more in depth.

Anyway I was playing with it writing some middlewares and I was wondering if the routes format limitations (like /users/:id) are due to the dependency with pathname-match which basically just strips away query params and anchors.

Would you consider replacing it with something more complex (probably), but yet more powerful and flexible like route-parser, in order to allow more complex routes configuration on a single middleware ?

I can gladly work on a PR about this in my spare time if you like this idea.

[bug] fastify missing in peerDep

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.20.2

Plugin version

No response

Node.js version

v15.10.0

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

3.13.5

Description

Problem

This lib requires fastify as a direct dependency but does not declare it in either dependencies or peerDependencies. This is not a problem in most cases if not using yarn v2, but it panics when yarn v2 forbids access to dependencies other than those explicitly declared in package.json.

This bug can also lead to errors if the installed version of fastify is not compatible with this lib.

Solution

Add fastify to peerDep with reasonable version requirement (e.g. ^3.0.0)

Steps to Reproduce

See description.

Expected Behavior

No response

Middie doesn't accept generics

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the issue has not already been raised

Issue

While I can use type generics in regular fastify like this:

fastify.get<{ Querystring: { id: number } }>('/', async (req , rep) => {
...
});

I cannot add type generics to use function. This code

fastify.use<{ Querystring: { id: number } }>('/', async (req, rep, next) => {
...
})

doesn't compile with the following error: TS2558: Expected 0 type arguments, but got 1. I think that use function should accept the same type aurguments as the fastify get, post functions.

Version 10 of node.js has been released

Version 10 of Node.js (code name Dubnium) has been released! 🎊

To see what happens to your code in Node.js 10, Greenkeeper has created a branch with the following changes:

  • Added the new Node.js version to your .travis.yml

If you’re interested in upgrading this repo to Node.js 10, you can open a PR with these changes. Please note that this issue is just intended as a friendly reminder and the PR as a possible starting point for getting your code running on Node.js 10.

More information on this issue

Greenkeeper has checked the engines key in any package.json file, the .nvmrc file, and the .travis.yml file, if present.

  • engines was only updated if it defined a single version, not a range.
  • .nvmrc was updated to Node.js 10
  • .travis.yml was only changed if there was a root-level node_js that didn’t already include Node.js 10, such as node or lts/*. In this case, the new version was appended to the list. We didn’t touch job or matrix configurations because these tend to be quite specific and complex, and it’s difficult to infer what the intentions were.

For many simpler .travis.yml configurations, this PR should suffice as-is, but depending on what you’re doing it may require additional work or may not be applicable at all. We’re also aware that you may have good reasons to not update to Node.js 10, which is why this was sent as an issue and not a pull request. Feel free to delete it without comment, I’m a humble robot and won’t feel rejected 🤖


FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

request.is is not a function, node_modules/@fastify/middie/lib/engine.js

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.6.2

Plugin version

No response

Node.js version

16

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

10

Description

I am using NestJS with fastify and I am trying to do file upload using graphql-upload.
I have to use the middleware graphqlUploadExpress where part it has checking,

function graphqlUploadExpressMiddleware(request, response, next) {
if (!request.is("multipart/form-data")) return next();
...
}

Hence, upon making a request an error is being prompted.
{"level":50,"time":1693409667838,"pid":40,"hostname":"a2ab2ffa0249","context":"ExceptionsHandler","err":{"type":"Error","message":"request.is is not a function","stack":"TypeError: request.is is not a function\n at graphqlUploadExpressMiddleware (/usr/src/app/node_modules/graphql-upload-ts/src/graphqlUploadExpress.ts:57:18)\n at Holder.done (/usr/src/app/node_modules/@fastify/middie/lib/engine.js:112:11)\n at Object.run (/usr/src/app/node_modules/@fastify/middie/lib/engine.js:59:12)\n at Object.runMiddie (/usr/src/app/node_modules/@fastify/middie/index.js:73:21)\n at hookIterator (/usr/src/app/node_modules/fastify/lib/hooks.js:353:10)\n at next (/usr/src/app/node_modules/fastify/lib/hooks.js:189:18)\n at hookRunner (/usr/src/app/node_modules/fastify/lib/hooks.js:211:5)\n at Object.routeHandler [as handler] (/usr/src/app/node_modules/fastify/lib/route.js:482:7)\n at Router.callHandler (/usr/src/app/node_modules/find-my-way/index.js:398:14)\n at Router.lookup (/usr/src/app/node_modules/find-my-way/index.js:376:17)"},"msg":"request.is is not a function"}

Steps to Reproduce

"dependencies": {
"@apollo/server": "4.9.0",
"@as-integrations/fastify": "2.1.0",
"@nestjs/apollo": "12.0.7",
"@nestjs/common": "10.1.3",
"@nestjs/config": "3.0.0",
"@nestjs/core": "10.1.3",
"@nestjs/graphql": "12.0.8",
"@nestjs/mongoose": "10.0.1",
"@nestjs/platform-fastify": "10.1.3",
"apollo-server-core": "^3.6.4",
"apollo-server-fastify": "^3.6.2",
"aws-sdk": "2.1437.0",
"express-jwt": "^7.7.7",
"graphql": "16.6.0",
"graphql-upload": "15.0.2",
"graphql-upload-ts": "^2.1.0",
"jwks-rsa": "^3.0.0",
"mongoose": "^6.2.1",
"nestjs-pino": "^2.5.0",
"newrelic": "^9.8.1",
"pino-http": "^6.6.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
},

Expected Behavior

No response

[request] re-evaluation reusify

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

🚀 Feature Proposal

Reusify does not hold the execution time optimization anymore, maybe it is the time to think about getting rid of it.

Motivation

reusify is used by the engine to hold Fastify instances. It declares ~10% execution time gain on massive creations of the same object. Over the past 4 years, node.js gets upgraded from v6 to v16, the built-in garbage collection and object instances management capabilities improved a lot.

I've done a benchmarking (using the provided "official" benchmarking scripts) with node v16.13.1:

$ node benchmarks/createNoCodeFunction.js
Total time 44847
Total iterations 100000000
Iteration/s 2229803.554306865

$ node benchmarks/reuseNoCodeFunction.js
Total time 45924
Total iterations 100000000
Iteration/s 2177510.669802282

The speed gain is negative. i.e. reusify slows the application down!

Does it still make sense to continue the "optimization"?

Example

No response

Error: fastify-plugin: middie - expected '>=3.0.0' fastify version, '2.14.1' is installed

🐛 Bug Report

fastify-plugin errors out as middie demands fastify version >=3.0.0 but the latest version is 2.14.1.

To Reproduce

Steps to reproduce the behavior:
Install fastify and middie.
Run the sample server code with middie usage.

Expected behavior

Fastify plugin should not report to install the version 3.0.0 which is not release yet. Looks like, it is in rc3 version.

Middie either should demand the rc3 version or demand the 2.14.1.

Your Environment

  • node version: 12
  • fastify version: >=2.14.1
  • os: Windows

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.