npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@sifrr/server

v0.0.9

Published

Server Side Redering for any js based app as a express middleware.

Downloads

38

Readme

sifrr-server · npm version Doscify

NodeJS Server based on uWebSocket.js with extended API to create static/api server.

Features

  • Extends uWebSocket.js
  • Simple static file serving with conditional last-modified, compression, cache, live reload support
  • Simple post request data, json data and form data handling (file upload, multipart, url-encoded)
  • easy graphql server

How to use

Do npm i @sifrr/server or yarn add @sifrr/server or add the package to your package.json file. And npm i uNetworking/uWebSockets.js#v15.11.0 or yarn add uNetworking/uWebSockets.js#v15.11.0 to install uWebSockets, which is a peerDependency needed.

Api

Basic usage

Sifrr Server extends 'uWebSockets.js' package. You can view more details here. So all the APIs from uWS works with sifrr server.

const { App, SSLApp } = require('@sifrr/server');
  • App extends uWS.App
  • SSLApp extends uWS.SSLApp

Extra APIs than uWS

createCluster

const { Cluster, App } = require('@sifrr/server');
const app = new App();
// do something on app
const app2 = new App();
// do something on app2
const cluster = new Cluster([
  {
    app: app,
    port: 12345
  },
  {
    app: app2,
    ports: [12346, 12347]
  }
]);

// listen on all ports
cluster.listen((uwsSocket, port) => {
  // this = app for port
  console.log(this, `is listening on port ${port}`);
});

// close all ports
cluster.close();

// close specific port
cluster.close(port);

writeHeaders

const { App, writeHeaders } = require('@sifrr/server');

const app = new App();
app.get('/', res => {
  writeHeaders(res, name, value); // single header
  writeHeaders(res, {
    name1: value1,
    name2: value2
  }); // multiple headers
});

sendFile

respond with file from filepath. sets content-type based on file name extensions, supports responding 304 based on if-modified-since headers, compression(gzip, brotli, deflate), range requests (videos, music etc.)

const { sendFile } = require('@sifrr/server');

const app = new App();
app.get(uWSRoutingPattern, (res, req) => {
  res.onAborted(e => process.stderr.write(e));
  sendFile(res, req, filepath, options);
});
  • options:
    • lastModified: default: true responds with 304 Not Modified for non-modified files if this is set to true
    • headers: default: {} Additional headers to set on response ,
    • compress: default: false responses are compressed if this is set to true and if accept-encoding header has supported compressions (gzip, brotli, deflate)
    • compressionOptions default: { priority: [ 'gzip', 'br', 'deflate' ] } which compression to use in priority, and other zlib options
    • cache: default: false, if given a node-cache-manager instance, it will cache the files in given cache. Generally it might not be needed at all, check for performance improvement before using it blindly.

Add additional mime type:

const { mimes } = require('@sifrr/server');
mimes['extension'] = 'mime/type';

host static files

  • Single file (alias for sendFile example above)

file from filepath will be server for given pattern

app.file(uWSRoutingPattern, filepath, options); // options are sendFile options
  • Folder

Whole folder will be server recursively under given prefix

app.folder(prefix, folder, options); // options are sendFile options

// Example
// if you have a file named `example.html` in folder `folder`, then doing this
app.folder('/example', folder, options);
// will serve example.html if you go to `/example/example.html`

Extra options overwriteRoute: if set to true, it will overwrite old pattern if same pattern is added. failOnDuplicateRoute: if set to true, it will throw error if you try add same pattern again. By default, it will serve the file you added first with a pattern. watch: if it is true, it will watch for new Files / deleted files and serve/unserve them as needed. livereload: default: false, more details here

Post requests

for post responses there are extra helper methods added to uWS response object (res is a response object given by Sifrr Server on post requests), note that as stream can only be used once, only one of these function can be called for one request:

  • res.body().then(body => /* do something */): gives post body as buffer
  • res.bodyStream(): Gives post body stream
  • res.json().then(jsonBody => /* do something */): gives post body as json if content-type is application/json (this method is only set if post body content-type is application/json)
  • res.formData(options).then(data => /* do something */) (only set if content-type is application/x-www-form-urlencoded or multipart/form-data)
res.formData(options).then(data => {
  // example data
  // {
  //   file: {
  //     filename: 'name.ext',
  //     encoding: '7bit',
  //     mimetype: 'application/json',
  //     filePath: 'tmpDir/name.ext' // only set if tmpDir is given
  //   },
  //   fieldname: value
  // }
});

options need to have atleast one of onFile function or tmpDir if body has files else request will timeout and formData() will never resolve.

  • if onFile is set, then it will be called with fieldname, file, filename, encoding, mimetype for every file uploaded, where file is file stream, you need to consume it or the request will never resolve
  • if tmpDir is given (folder name), files uploaded will be saved in tmpDir, and filePath will added in given data if filename function is given, it will be called with original filename, and name returned will be used when saving in tmpDir.
  • onField (optional): will be called with fieldname, value if given
  • other busboy options

Array fields/files:

  • if fieldname is something and it has multiple values, then data.something will be an array else it will be a single value.
  • if fieldname is something[] then data.something will always be an array with >=1 values.

graphql server

function contextFxn(res, err) {
  // return context value
  return {
    user: {
      id: 1
    }
  }
}

const graphqlSchema = /* get graphql executable schema from somewhere (Javascript one, not graphql dsl) */;

app.graphql('/graphql', graphqlSchema, contextFxn);

It supports:

  • POST requests with query params (query and variables) eg. /graphql?query=query($id: String) { user(id: $id) { id \n name } }&variables={"id":"a"}

  • POST requests with json body (containing query and variables) eg body:

{
  query: `
    query($id: String) {
      user(id: $id) {
        id
        name
      }
    }`,
  variables: {
    id: 'b'
  }
}
  • Websocket subscriptions (same message format as graphql-subscription-ws)
// subscribe message
{
  type: 'start',
  payload: {
    query: ``, // subscription query
    variables: {...}
  }
}

// unsubscribe message
{
  type: 'stop',
  id: 1 // subscription id received when subscribing
}

can be implemented easily using sifrr-fetch

Live reload (experimental)

Live reload, reloads browser page when static files are changed or a signal is sent.

const { App } = require('@sifrr/server');

const app = new App();
app.folder('/live', folderPath, {
  livereload: true // ideally true only in development
  // other sendFile options
});

// then in your frontend js file
import livereload from '@sifrr/server/src/livereloadjs';
<!-- or with script tag -->
<script src="/livereload.js"></script>
<!-- don't overwrite this path if you using it -->

Load routes

An example route file:

const path = require('path');

const headers = {
  'access-control-allow-origin': '*',
  'access-control-allow-methods': '*',
  Connection: 'keep-alive'
};

module.exports = {
  basePath: '/p', // this preffix will be added to all the routes in this file
  folder: {
    '': [path.join(__dirname, '../public'), { headers, lastModified: false }],
  },
  get: {
    '/some': (res, req) => res.send('ABD');
  }
};

You can have multiple route files in a folder, and then you can call

app.load(dirPath, { filter: filepath => true, basePath: '' });

And all the routes from the route files in this directory will be added to your app server.

for example the above route file will add following routes:

app.folder('/p', path.join(__dirname, '../public'), { headers, lastModified: false });
app.get('/p/some', (res, req) => res.send('ABD'));

Options:

  • filter - this function will be called with all filepaths in directory, and if this returns true that route file will be added, else it will be not.
  • basePath - base path preffix to add for all the routes

Static server Benchmarks

From this file

# small static file
┌─────────┬──────┬───────────────┬───────────────┬─────────────┬─────────────────────┐
│ (index) │ rps  │ meanLatencyMs │ totalRequests │ totalErrors │  totalTimeSeconds   │
├─────────┼──────┼───────────────┼───────────────┼─────────────┼─────────────────────┤
│  sifrr  │ 1720 │      4.6      │      500      │      0      │     0.290736455     │
│ express │ 1510 │      5.1      │      500      │      0      │ 0.33112339300000004 │
└─────────┴──────┴───────────────┴───────────────┴─────────────┴─────────────────────┘
# big file
┌─────────┬─────┬───────────────┬───────────────┬─────────────┬────────────────────┐
│ (index) │ rps │ meanLatencyMs │ totalRequests │ totalErrors │  totalTimeSeconds  │
├─────────┼─────┼───────────────┼───────────────┼─────────────┼────────────────────┤
│  sifrr  │ 797 │      10       │      500      │      0      │    0.627346613     │
│ express │ 767 │     10.3      │      500      │      0      │ 0.6518107169999999 │
└─────────┴─────┴───────────────┴───────────────┴─────────────┴────────────────────┘
# big file with gzip compression
┌─────────┬─────┬───────────────┬───────────────┬─────────────┬──────────────────┐
│ (index) │ rps │ meanLatencyMs │ totalRequests │ totalErrors │ totalTimeSeconds │
├─────────┼─────┼───────────────┼───────────────┼─────────────┼──────────────────┤
│  sifrr  │ 397 │     19.9      │      398      │      0      │    1.00134455    │
│ express │ 329 │     24.1      │      329      │      0      │   1.001440561    │
└─────────┴─────┴───────────────┴───────────────┴─────────────┴──────────────────┘
# big file with cache vs normal express
┌─────────┬─────┬───────────────┬───────────────┬─────────────┬────────────────────┐
│ (index) │ rps │ meanLatencyMs │ totalRequests │ totalErrors │  totalTimeSeconds  │
├─────────┼─────┼───────────────┼───────────────┼─────────────┼────────────────────┤
│  sifrr  │ 921 │      8.6      │      500      │      0      │ 0.5427590760000001 │
│ express │ 767 │     10.3      │      500      │      0      │    0.651958354     │
└─────────┴─────┴───────────────┴───────────────┴─────────────┴────────────────────┘
# big file gzip compressin and cache vs normal express with compression
┌─────────┬─────┬───────────────┬───────────────┬─────────────┬────────────────────┐
│ (index) │ rps │ meanLatencyMs │ totalRequests │ totalErrors │  totalTimeSeconds  │
├─────────┼─────┼───────────────┼───────────────┼─────────────┼────────────────────┤
│  sifrr  │ 491 │     16.1      │      493      │      0      │    1.004362406     │
│ express │ 357 │      22       │      358      │      0      │ 1.0017989919999999 │
└─────────┴─────┴───────────────┴───────────────┴─────────────┴────────────────────┘

Examples

Are available in test/public/benchmarks/sifrr.js