Skip to content

Commit bae85bc

Browse files
committed
chore(example): http server example
Adds an example project in ./examples that demonstrates hosting an instance of Hold This as a HTTP server.
1 parent 5691d1f commit bae85bc

File tree

6 files changed

+679
-0
lines changed

6 files changed

+679
-0
lines changed

README.md

+37
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ It is designed to be used in a single-threaded synchronous environment.
88
[Examples](#examples)
99
- [No Config](#no-config-setup)
1010
- [File Backed](#file-backed-store)
11+
- [HTTP Server](#http-server)
1112
- [WebSocket Server](#websocket-server)
1213
- [Data Serialization](#data-serialization)
1314
- [TTL / Expiring Records](#ttl-%2F-expiring-records)
@@ -103,6 +104,42 @@ const holder = holder({ location: './holder.sqlite', enableWAL: false })
103104
_Performed on Macbook Pro M1 with 16 GB Memory_
104105

105106

107+
### HTTP Server
108+
109+
Because Hold This is based on SQLite3, it does not support a native network connection.
110+
We can achieve a networked, multi-connection instance by wrapping Hold This in a HTTP Server.
111+
112+
>[!CAUTION]
113+
>This is not a production ready example. Security, and failure modes must be considered, but are outside the scope of the example.
114+
115+
```js
116+
import { createServer } from 'node:http'
117+
import Hold from 'hold-this'
118+
119+
const server = createServer()
120+
server.holder = Hold()
121+
122+
server.on('request', (req, res) => {
123+
let body = ''
124+
req.on('data', (chunk) => { body += chunk })
125+
126+
req.on('end', () => {
127+
const parsedBody = JSON.parse(body)
128+
129+
const { cmd, topic, key, value, options } = parsedBody
130+
const data = server.holder[cmd](topic, key, value, options)
131+
132+
res.writeHead(200, { 'Content-Type': 'application/json' })
133+
res.end(JSON.stringify(data))
134+
})
135+
})
136+
137+
server.listen(3000)
138+
```
139+
140+
_The complete example including client and benchmarks can be found in [examples/http](examples/http)_
141+
142+
106143
### WebSocket Server
107144

108145
Because Hold This is based on SQLite3, it does not support a native network connection.

examples/http/README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Hold This Example: HTTP
2+
=======================
3+
4+
This is an example application that runs Hold This behind an HTTP server.
5+
6+
>[!CAUTION]
7+
>This is not a production ready example. Security, and failure modes must be considered, but are outside the scope of the example.
8+
9+
>[!NOTE]
10+
>This example depends on the package `autocannon` to benchmark the server.
11+
>
12+
>Requires Node 22.0.0
13+
>- import.meta.filename
14+
15+
Getting Started
16+
---------------
17+
18+
1. Install dependency
19+
20+
npm install
21+
22+
2. Run Example
23+
24+
npm start
25+
26+
3. Run Benchmark
27+
28+
npm run test:bench

examples/http/bench.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { stdout } from 'node:process'
2+
import autocannon from 'autocannon'
3+
import { Server } from './index.js'
4+
5+
const server = Server()
6+
7+
server.holder.set('http', 'foo', 'bar')
8+
9+
autocannon({
10+
url: 'http://localhost:3000',
11+
connections: 10,
12+
pipelining: 10,
13+
duration: 10,
14+
method: 'POST',
15+
headers: {
16+
'Content-Type': 'application/json'
17+
},
18+
body: JSON.stringify({
19+
cmd: 'get', topic: 'http', key: 'test'
20+
})
21+
22+
}, (err, result) => {
23+
if (err) throw err
24+
25+
stdout.write(autocannon.printResult(result))
26+
server.close()
27+
})

examples/http/index.js

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { createServer } from 'node:http'
2+
import Hold from 'hold-this'
3+
4+
/**
5+
* Creates a HTTP server decorated with an instance of the data store
6+
*
7+
* @param {Object} options - server options
8+
* @param {number} options.port - port number to listen on
9+
* @returns {http.Server}
10+
* @example
11+
* const server = Server()
12+
*/
13+
export function Server ({ port = 3000 } = {}) {
14+
const server = createServer()
15+
server.holder = Hold()
16+
17+
server.on('request', (req, res) => {
18+
if (req.method !== 'POST') {
19+
res.writeHead(405, { 'Content-Type': 'application/json' })
20+
res.end('Method Not Allowed\n')
21+
return false
22+
}
23+
24+
if (req.headers['content-type'] !== 'application/json') {
25+
res.writeHead(415, { 'Content-Type': 'application/json' })
26+
res.end('Unsupported Media Type\n')
27+
return false
28+
}
29+
30+
let body = ''
31+
req.on('data', (chunk) => { body += chunk })
32+
33+
req.on('end', () => {
34+
const parsedBody = JSON.parse(body)
35+
36+
const { cmd, topic, key, value, options } = parsedBody
37+
const data = server.holder[cmd](topic, key, value, options)
38+
39+
res.writeHead(200, { 'Content-Type': 'application/json' })
40+
res.end(JSON.stringify(data))
41+
})
42+
})
43+
44+
server.listen(port)
45+
46+
return server
47+
}
48+
49+
// When this file is run directly from node (main module)
50+
// setup a server, and run a test demonstrating using fetch to set and
51+
// get data from the server
52+
if (process.argv[1] === import.meta.filename) {
53+
Server()
54+
55+
// Write the record that we'll be fetching
56+
await fetch('http://localhost:3000', {
57+
headers: {
58+
'Content-Type': 'application/json'
59+
},
60+
method: 'POST',
61+
body: JSON.stringify({
62+
cmd: 'set', topic: 'http', key: 'test', value: 'test1'
63+
})
64+
})
65+
66+
// Fetch the record previously set
67+
const response = await fetch('http://localhost:3000', {
68+
headers: {
69+
'Content-Type': 'application/json'
70+
},
71+
method: 'POST',
72+
body: JSON.stringify({
73+
cmd: 'get', topic: 'http', key: 'test'
74+
})
75+
}).then(response => response.json())
76+
77+
console.log(response)
78+
}

0 commit comments

Comments
 (0)