DEV Community

Paul Rumkin
Paul Rumkin

Posted on • Updated on

Creating JS web server without Node.js

Today there are exists a lot of web servers created for Node.js and it's hard to tell which one is better. Engineers are working to add something new into their products and using the last JS and v8 abilities to bring faster, handy and effective solutions. And I'm working on one too. It's Plant. In this article I will tell about Plant's ability to work without Node.js.

Plant is a WebAPI charged web server, what means it's using the same interfaces as browsers do. Also it's transport agnostic and you can deliver requests using anything: TCP, WebSocket, WebRTC, etc. Tying all this up it could work in a browser and on a server in the same way. What this ability gives to us?

  • We can develop a web server using only code editor and web browser, using DevTool's debugger instead of console.log for troubleshooting and then just move it to Node.js.
  • We don't even need to install anything.
  • It makes tests more reliable and simple to write, run and understand.
  • Easy to learn: code is isolated into browser and free of environment influence.
  • Truly crossplatform: it works in the same way everywhere.

TL;DR. Here it is the simple demo and complex demo.

Demo repo · Plant repo

Well let's start. All you need to start is to create simple HTML file with empty page.

<!DOCTYPE html>
<html>
  <head></head>
  <body></body>
</html>

Import

To use Plant in your web page just import it from unpkg.com:

<script src="https://unpkg.com/@plant/plant@2.4.0/dist/plant.js"></script>

Put it into <head>. Then create new <script> into <body> element for our application.

Create server

Server creation is similar to Express' or Koa's with small differences.

// Instantiate server
const plant = new Plant()

// Add handler
plant.use('/index.html', ({req, res}) => {
  res.html('<!DOCTYPE html><html><body>Hello</body></html>')
})

That's it. We created a very simple request handler which serves '/index.html' page. Note that route is very strict. It would handle /index.html but not /.

Making request

Browsers couldn't listen requests and we will simulate it on ourselves. This code could be used to handle requests delivered by WebSocket or any other transport, so it could be used by anything.

This is our final code which will de explained later:

const handle = plant.getHandler()

const context = createContext('GET', 'https://localhost:8000/index.html')

handle(context)
.then(({res}) => {
  res.status // -> 200
  res.body // -> '<!DOCTYPE html>...
})

Creating context

Plant context is a simple Object instance. It requires Socket, Request and Response instances to be passed as socket, req and res properties respectively.

function createContext(method, url, {headers = {}, body} = {}) {
  const req = new Plant.Request({
    url: new URL(url),
    method,
    body,
    headers,
  })

  const res = new Plant.Response({
    url: new URL(url),
  })

  const socket = new Plant.Socket({
    peer: new Plant.Peer({
      uri: new Plant.URI({
        protocol: 'browser',
        host: navigator.appName,
      })
    })
  })

  return {req, res, socket}
}

Socket requires a peer instance which represents another party of a connection. Peer should always have an address provided as URI. URI is using here instead of standard URL due to URL's inability to work with custom schemes. All this needed due we suggest that peer could receive connections too, because Plant can use transport supporting this ability.

POST requests

To simulate POST request we need to create ReadableStream instance and pass it into Request constructor.

const body = streamFrom('Hello')

new Request({
  method: 'GET',
  url: 'https://localhost:8000',
  body,
})

function streamFrom(value) {
  return new ReadableStream({
    start(controller) {
      // Encode UTF8 into Uint8Array and push it into the stream
      const encoder = new TextEncoder()
      controller.enqueue(encoder.encode(value))
      controller.close()
    },
  })
}

Function createStream creates ReadableStream instance and write whole passed value as a single chunk into it.

Conclusion

Now you can put it all together and emulates request right in a browser on your own without using Node.js. Or you can get write and debug your API in a browser and then move it to Node.js with minimal changes. And the same code could be used in tests.

If you meet some difficulties or want to check yourself use simple example or more complex demonstration.

Repositories

GitHub logo rumkin / plant

🌳 JS web server charged with WebAPI and neat HTTP2 support

Plant logo

Plant

npm npm

NPM · Source · Readme

Plant is WebAPI standards based HTTP2 web server, created with modular architecture and functional design in mind. Also it's pure and less coupled.

Plant supports HTTP 1 and HTTP 2 protocols. But it's transport agnostic and can work right in the browser over WebSockets, WebRTC, or PostMessage.

Features

  • ☁️ Lightweight: only 8 KiB minified and gzipped.
  • Serverless ready: works even in browser.
  • 🛡 Security oriented: uses the most strict Content Securiy Policy (CSP) by default.
  • 📐 Standards based: uses WebAPI interfaces.
  • 🛳 Transport agnostic: no HTTP or platform coupling, ship requests via everything.

Table of Contents

Install

# Install plant web server
npm i @plant/plant
# Install node HTTP2 transport
npm i @plant/http2

Examples

Hello World

Hello world with HTTP2 as transport.

⚠️ Note that default CSP header value is default-src localhost; form-action localhost This will…

GitHub logo rumkin / plant-browser-demo

Nodeless web server demo

Plant Browser Demo

This is a single-file demo web application. Its' purpose is to show how to develop fully functional web server with test coverage without Node.js using only browser and code editor.

Usage

  1. Save page on disk.
  2. Open it in code editor.
  3. Update #Server and #Spec scripts code.
  4. Refresh the page. DevTools console should contain complete output of your test.

Dependencies

Structure overview

Code of the example is structured into several scripts. Each script contains code related to specific task.

<script id="Server"></script>
<script id="Spec"></script>
<script id="TestupHelpers"></script>
<script id="PlantHelpers"></script>
<script id="Main"></script>

Where:

  • #Server. Your http API code.
  • #Spec. Your tests for the…

Top comments (3)

Collapse
 
melroysblog profile image
Melroy's Blog

So this is not a good practice right. But just a nice experiment.

Collapse
 
miku86 profile image
miku86

Why?

Collapse
 
rumkin profile image
Paul Rumkin • Edited

It’s a production-ready solution. If you have tests and this tests pass in a browser and node.js, then you can develop in browser and run code in node.