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

rest-over-sockets

v2.1.0

Published

REST without HTTP over (web)sockets

Downloads

10

Readme

REST-over-sockets

REST API over (Web)sockets using an Express style interface

  • 100% native javascript
  • Compatible with Websockets, Sockhop and socket.io
  • Uses express-style controller declarations (see Asseverate if you want to write rest-over-sockets- and express-compatible controllers)
  • Supports parameter capture, automatic error handling, and response encoding

Why

You have an application whose only clients connect directly over TCP/IP or your clients all support Websockets/socket.io and
you don't see the point in programming multiple API endpoints - some REST over HTTP, some over Websockets.

Details

Check out the full documentation here

Request

Incoming requests are simple native object, presumably transmitted over the wire using JSON. You can do this yourself, or you can use a library like Sockhop or socket.io -- depending on if you are in browser or not. Alternatively, you can use the ROSRequest object together with the .toJSON() method to ensure a correct format.

| Parameter | Type | Example | Required | Notes | |-----------|---------|----------------------------------------|----------|--------------------------------------------------------| | method | string | "POST" | Y | Must be upper-case | | path | string | "/photos/cat.jpg" | Y | | | header | ?object | { "content-type": "application/json" } | N | Ought to be lower-case keys, though this is coerced on the server | | body | ?object | { "some": "data" } | N | | | params | object | { id: 23 } | | RESERVED - auto populated from URL capture parameters | | query | object | { limit: 100 } | | RESERVED - auto populated from URL capture parameters |

{
    "method": "GET",
    "path": "/apple/3444"
}

Response

If your handler throws an exception, the error will automatically result in a HTTP style 500 response. Routes that don't exist return a HTTP style 404 error. The response body will look like this. Note that the headers are coerced to be lower-case, following the express.js convention for headers.

{
    "statusCode": 200,
    "headers": {
        "content-type": "application/json"
    },
    "data": [
        {
            "type": "Apple",
            "id": "23",
            "attributes": {
                "flavor": "sweet"
            }
        }
    ]
}

Examples

Checkout the examples folder for the source code:

Raw Websockets

Server using (raw) Websockets

const wss=new (require("ws")).Server({ port: 8080 });
const restos=new (require("rest-over-sockets"))();

// Set up a server
wss.on("connection", (ws)=>{

  ws.on("message", (message)=> {

    restos.receive(JSON.parse(message),(response)=>{

      ws.send(JSON.stringify(response));
    });
  });
});
 
// Add an Express-style route
restos.get("/widget/:id", (req, res)=>{

    res
      .set('Content-Type', 'text/json') // note, this gets coerced lower-case
        .status(200)
        .json([{ type:"Apple", id:req.params.id, attributes:{ flavor: "sweet" }}])
});

Client using (raw) Websockets

const ws= new (require("ws"))("ws://localhost:8080/");

ws.on("open", ()=>{

  ws.send(JSON.stringify({

    method : "GET",
    path : "/widget/23"

  }));
});

ws.on("message", (data)=>{

  console.log(data);  // {"status":200,"headers":{"content-type":"text/json"},"data":[{"type":"Apple","id":"23","attributes":{"flavor":"sweet"}}]}
  ws.close();
});

Of course, Websockets has it's limitations, and so these days lots of people are using socket.io as a nice abstraction layer to handle these details. Rest-over-sockets integrates nicely into your pre-existing socket.io infastructure

Socket.io

Server using socket.io

const http = require('http');
const { Server:IOServer } = require('socket.io');

const server = http.createServer();
server.listen(3000, () => {
    console.log("Listening on http://localhost:3000");
});

const io = new IOServer(server);
const restos=new (require("rest-over-sockets"))();

// Set up a server
io.on("connection", (sock)=>{
    // Request will be emitted on "ROSRequest" by the ROSClient
    sock.on("ROSRequest", (msg, callback)=> {
        console.log("Received message", msg);
        restos.receive(msg,callback);
    });
});
 
// Add an Express-style route
restos.get("/widget/:id", (req, res)=>{
    res
    .set('Content-Type', 'text/json') // note, this gets coerced lower-case
    .status(200)
    .json([{ type:"Apple", id:req.params.id, attributes:{ flavor: "sweet" }}])
});

Client using socket.io

const { ROSClient } = require("rest-over-sockets");
const { io } = require('socket.io-client');

const socket = io('http://localhost:3000');
const client = ROSClient.socketio(socket);

socket.on("connect", ()=>{
    client.get("/widget/3444").then(response=>{
        console.log(`Response: ${JSON.stringify(response)}`);
        socket.disconnect();
    });
});

Socket.io is great, but sometimes you are using native (i.e. tcp or unix) sockets, In that case: try Sockhop, since it will automatically handle reconnections, remote callbacks to ensure the response is given to the request that called it, and also JSON encoding and possible packetization / fragmentation across the wire. Basically, it fixes all the nasty edge-cases of raw tcp sockets so that you can just focus on the real work of writing the interface.

Sockhop

Server using Sockhop callbacks (old-style)

const server=new (require("sockhop").server)();
const restos=new (require("rest-over-sockets"))();

server.listen();
// Assume everything that comes over the wire is a ROSRequest
server.on("receive", (o, meta)=>restos.receive(o, meta.callback));

restos.get("/apple/:id", (req, res)=>{
    res
      .set('Content-Type', 'text/json') // note, this gets coerced lower-case
        .status(200)
        .json([{ type:"Apple", id:req.params.id, attributes:{ flavor: "sweet" }}])
});

Client using Sockhop callbacks (old-style)

const client=new (require("sockhop").client)();

client.connect().then(()=>{

  client.send({
    method: "GET",
    path: "/apple/3444"
  },(response)=>{

    console.log(`Response: ${JSON.stringify(response)}`);
    client.disconnect();
  });
});

Server using Sockhop requests (new-style)

const server=new (require("sockhop").server)();
const restos=new (require("rest-over-sockets"))();

server.listen();
server.on("request", (req,res,meta)=> {
    // Request will be sent as a "ROSRequest" type by the ROSClient
    if ( req.type !== "ROSRequest" ) return; // ignore other types
    restos.receive(req.data, (obj) => res.send(obj))
});

restos.get("/apple/:id", (req, res)=>{
    res
      .set('Content-Type', 'text/json') // note, this gets coerced lower-case
        .status(200)
        .json([{ type:"Apple", id:req.params.id, attributes:{ flavor: "sweet" }}])
});

Client using Sockhop requests (new-style)

const { ROSClient } = require("rest-over-sockets");
const sock=new (require("sockhop").client)();
const client = ROSClient.sockhop(sock);

sock.connect().then(()=>{
    return client.get("/apple/3444")
}).then(response => {
    console.log(`Response: ${JSON.stringify(response)}`);
    sock.disconnect();
});

Notes

Make sure your handlers (added by calling .get(), .post(), etc) run asynchronously. Example:

// BAD!
restos.get("/some/path", (req, res)=>{
  
    NASTY_BLOCKING_TASK();

    /* ... */

    res.send();  
});


// GOOD
restos.get("/some/path", (req, res)=>{
  
    return new Promise((resolve)=>{

      NASTY_BLOCKING_TASK();

      /* ... */

      resolve();
    })
    .then(res.send);
});

TODO

  • Add cookie support?
  • Support streaming?
  • Support more content types?
  • Default 404 and 500 message types

License

MIT