@server/next 0.20.42 → 0.20.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -1
- package/src/helpers/jsx.js +27 -0
- package/src/index.js +3 -3
- package/readme.md +0 -275
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@server/next",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.44",
|
|
4
4
|
"description": "A fully-fledged web server with routing, file uploads, sessions, static files, schema validation, websockets, testing, etc.",
|
|
5
5
|
"homepage": "https://server-js.com/",
|
|
6
6
|
"repository": "https://github.com/franciscop/server-next.git",
|
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
"funding": "https://www.paypal.me/franciscopresencia/19",
|
|
9
9
|
"author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
|
+
"documentation": {
|
|
12
|
+
"title": "Server JS - A modern web server for Bun and Node.js",
|
|
13
|
+
"menu": {
|
|
14
|
+
"About": "/about",
|
|
15
|
+
"Documentation": "/documentation",
|
|
16
|
+
"Github": "https://github.com/franciscop/server-next"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
11
19
|
"scripts": {
|
|
12
20
|
"start": "bun test --watch",
|
|
13
21
|
"test": "bun test",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const entities = { "&": "&", "<": "<", ">": ">", '"': """ };
|
|
2
|
+
const encode = (str = "") => str.replace(/[&<>"]/g, (tag) => entities[tag]);
|
|
3
|
+
|
|
4
|
+
const SELFCLOSE = new Set(
|
|
5
|
+
"area,base,br,col,embed,hr,img,input,link,meta,source,track,wbr".split(","),
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
export const jsx = (tag, { children, ...props }) => {
|
|
9
|
+
if (typeof tag === "function") return tag({ children, ...props });
|
|
10
|
+
|
|
11
|
+
if (SELFCLOSE.has(tag)) return `<${tag} />`;
|
|
12
|
+
if (props.dangerouslySetInnerHTML)
|
|
13
|
+
children = props.dangerouslySetInnerHTML.__html;
|
|
14
|
+
if (!children) children = [];
|
|
15
|
+
if (typeof children === "string") children = [children];
|
|
16
|
+
children = (children || [])
|
|
17
|
+
.map((c) => (typeof c === "function" ? c() : encode(c)))
|
|
18
|
+
.join("");
|
|
19
|
+
if (!tag) return () => children;
|
|
20
|
+
const attrStr = Object.entries(props || {})
|
|
21
|
+
.filter(([k, v]) => !/on[A-Z]/.test(k) && typeof v !== "function")
|
|
22
|
+
.map(([k, v]) => `${encode(k)}="${encode(v)}"`)
|
|
23
|
+
.join(" ");
|
|
24
|
+
return () => `<${tag} ${attrStr}>${children}</${tag}>`;
|
|
25
|
+
};
|
|
26
|
+
export const jsxDEV = jsx;
|
|
27
|
+
export const Fragment = "";
|
package/src/index.js
CHANGED
|
@@ -79,11 +79,11 @@ server.prototype.self = function () {
|
|
|
79
79
|
// #region Runtimes
|
|
80
80
|
// Node.js
|
|
81
81
|
server.prototype.node = async function () {
|
|
82
|
+
const options = config(this.opts);
|
|
82
83
|
const http = await import("http");
|
|
83
84
|
http
|
|
84
85
|
.createServer(async (request, response) => {
|
|
85
86
|
try {
|
|
86
|
-
const options = config(this.opts);
|
|
87
87
|
const ctx = await createNodeContext(request, options, this);
|
|
88
88
|
const out = await handleRequest(this.handlers, ctx);
|
|
89
89
|
|
|
@@ -100,7 +100,7 @@ server.prototype.node = async function () {
|
|
|
100
100
|
response.end();
|
|
101
101
|
}
|
|
102
102
|
})
|
|
103
|
-
.listen(
|
|
103
|
+
.listen(options.port);
|
|
104
104
|
};
|
|
105
105
|
|
|
106
106
|
// Netlify
|
|
@@ -210,7 +210,7 @@ server.prototype.test = function () {
|
|
|
210
210
|
options.headers.cookie = cookie;
|
|
211
211
|
}
|
|
212
212
|
const res = await this.fetch(
|
|
213
|
-
new Request("http://localhost:3000" + path, options)
|
|
213
|
+
new Request("http://localhost:3000" + path, options),
|
|
214
214
|
);
|
|
215
215
|
|
|
216
216
|
const headers = parseHeaders(res.headers);
|
package/readme.md
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
# Documentation
|
|
2
|
-
|
|
3
|
-
> **⚠️ WIP** This is an **experimental library** right now!
|
|
4
|
-
|
|
5
|
-
A fully-fledged web server for Bun and Node.js, with all the basics built-in:
|
|
6
|
-
|
|
7
|
-
```js
|
|
8
|
-
import server from "@server/next";
|
|
9
|
-
|
|
10
|
-
export default server(options)
|
|
11
|
-
.get("/books", () => Book.list())
|
|
12
|
-
.post("/books", BookSchema, (ctx) => {
|
|
13
|
-
return Book.create(ctx.body).save();
|
|
14
|
-
});
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
It includes all the things you would expect from a modern Server framework, like routing, static file serving, body+file parsing, gzip+brotli, streaming, testing, error handling, websockets, etc.
|
|
18
|
-
|
|
19
|
-
We also have integrations and adaptors for these:
|
|
20
|
-
|
|
21
|
-
- KV Stores: in-memory, Redis, Consul, DynamoDB, [Level](https://github.com/Level/level).
|
|
22
|
-
- Buckets: AWS S3, Cloudflare R2, Backblaze B2.
|
|
23
|
-
- Validation libraries: [Zod](https://zod.dev/), [Joi](https://joi.dev/), [Yup](https://github.com/jquense/yup), [Validate](https://validatejs.org), etc.
|
|
24
|
-
- Auth: JWT, Session, Cookies, Social login.
|
|
25
|
-
|
|
26
|
-
```js
|
|
27
|
-
// How to test your server - index.test.js
|
|
28
|
-
import app from "./";
|
|
29
|
-
const api = app.test(); // Very convenient helper, AXIOS-like interface
|
|
30
|
-
|
|
31
|
-
it("can retrieve the book list", async () => {
|
|
32
|
-
const { data: books } = await api.get("/books/");
|
|
33
|
-
expect(books[0]).toEqual({ id: 0, name: ... });
|
|
34
|
-
});
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Getting started
|
|
38
|
-
|
|
39
|
-
First install it:
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
npm install @server/next
|
|
43
|
-
yarn add @server/next
|
|
44
|
-
bun install @server/next
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Now you can create your first simple server:
|
|
48
|
-
|
|
49
|
-
```js
|
|
50
|
-
// index.js
|
|
51
|
-
import server from "@server/next";
|
|
52
|
-
|
|
53
|
-
export default server()
|
|
54
|
-
.get("/", () => "Hello world")
|
|
55
|
-
.post("/", (ctx) => {
|
|
56
|
-
console.log(ctx.body);
|
|
57
|
-
return 201;
|
|
58
|
-
});
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Then run `node .` or `bun .` and open your browser on http://localhost:3000/ to see the message.
|
|
62
|
-
|
|
63
|
-
There are some major configuration options that you might want to set up though, enumerated in the [Basic Usage](#basic-usage) and explained through the docs.
|
|
64
|
-
|
|
65
|
-
## Basic usage
|
|
66
|
-
|
|
67
|
-
Now that you know how to create a barebones server, there are some important bits that you might want to update.
|
|
68
|
-
|
|
69
|
-
`SECRET`: create an `.env` file (ignored in git with `.gitignore`) with the `SECRET=` and then a long, unique random secret. It will be used to sign and/or encrypt things as needed.
|
|
70
|
-
|
|
71
|
-
`store`: almost anything you want to persist will need a KV store to do so. For dev you can use an in-memory or a file-based (easy for debugging!) store, but for production systems you would normally use something like Redis. Can be as easy as `const store = new Map();` for dev.
|
|
72
|
-
|
|
73
|
-
> Note: `bucket` is still _not_ available
|
|
74
|
-
|
|
75
|
-
`bucket`: if you want to accept user files you will need a place to persist them. By default they are put in the filesystem, but since most cloud providers are ephemeral, provide a `bucket` and it will be used to handle the files directly.
|
|
76
|
-
|
|
77
|
-
An example of how that works in practice:
|
|
78
|
-
|
|
79
|
-
```js
|
|
80
|
-
// index.js
|
|
81
|
-
import server from "@server/next";
|
|
82
|
-
|
|
83
|
-
import Bucket from "bucket/b2";
|
|
84
|
-
import { createClient } from "redis";
|
|
85
|
-
|
|
86
|
-
const bucket = Bucket("mybucketname", { id, key });
|
|
87
|
-
const store = createClient("...").connect();
|
|
88
|
-
|
|
89
|
-
export default server({ bucket, store })
|
|
90
|
-
.get("/", () => "Hello world")
|
|
91
|
-
.post("/", (ctx) => {
|
|
92
|
-
console.log(ctx.body);
|
|
93
|
-
return 201;
|
|
94
|
-
});
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## Guides
|
|
98
|
-
|
|
99
|
-
### Middleware
|
|
100
|
-
|
|
101
|
-
### Validation
|
|
102
|
-
|
|
103
|
-
### Stores
|
|
104
|
-
|
|
105
|
-
### File handling
|
|
106
|
-
|
|
107
|
-
To manage files, you need to install and use the library [`bucket`](http://bucketjs.com/), which is a very thin wrapper for file management systems. It is also created by the makers of Server.js.
|
|
108
|
-
|
|
109
|
-
The easiest and default is to set a folder in your filesystem:
|
|
110
|
-
|
|
111
|
-
```js
|
|
112
|
-
import FileSystem from "bucket/fs";
|
|
113
|
-
|
|
114
|
-
const uploads = FileSystem("./uploads");
|
|
115
|
-
|
|
116
|
-
// All paths are relative to the CWD
|
|
117
|
-
export default server({ uploads })
|
|
118
|
-
.get("/", () => "Hello")
|
|
119
|
-
.put("/users/:id", async (ctx) => {
|
|
120
|
-
// This is the plain string as the file name, already in our FS
|
|
121
|
-
const fileName = ctx.body.profile;
|
|
122
|
-
// 'yOuZEdSsNLq8PgZyLhSz0Llh.jpg'
|
|
123
|
-
|
|
124
|
-
// Convert it into a File instance
|
|
125
|
-
const file = ctx.uploads.file(fileName);
|
|
126
|
-
|
|
127
|
-
// Now we can use other methods if we want
|
|
128
|
-
// .info(), .read(), .write(), .pipe(), .pipeTo(), etc
|
|
129
|
-
const info = await file.info();
|
|
130
|
-
// {
|
|
131
|
-
// id: "yOuZEdSsNLq8PgZyLhSz0Llh.jpg",
|
|
132
|
-
// type: "jpg",
|
|
133
|
-
// size: 435435,
|
|
134
|
-
// timestamp: "2024-08-07T14:26:37Z",
|
|
135
|
-
// // Note: this can be customized providing the option "domain"
|
|
136
|
-
// url: "file:///Users/me/my-project/uploads/yOuZEdSsNLq8PgZyLhSz0Llh.jpg",
|
|
137
|
-
// }
|
|
138
|
-
|
|
139
|
-
return 200;
|
|
140
|
-
});
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
To upload the files to a 3rd party system, you just need to use the corresponding `bucket` implementation (or write a thin compatibility layer). Let's see an example with Backblaze's B2:
|
|
144
|
-
|
|
145
|
-
```js
|
|
146
|
-
import server from "@server/next";
|
|
147
|
-
import Backblaze from "bucket/b2";
|
|
148
|
-
|
|
149
|
-
const uploads = Backblaze("bucket-name", {
|
|
150
|
-
id: process.env.BACKBLAZE_ID,
|
|
151
|
-
key: process.env.BACKBLAZE_KEY,
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
export default server({ uploads })
|
|
155
|
-
.put("/users/:id", async (ctx) => {
|
|
156
|
-
const fileName = ctx.body.profile;
|
|
157
|
-
// 'yOuZEdSsNLq8PgZyLhSz0Llh.jpg'
|
|
158
|
-
|
|
159
|
-
// Convert it into a File instance
|
|
160
|
-
const file = ctx.uploads.file(fileName);
|
|
161
|
-
|
|
162
|
-
// Now we can use other methods if we want
|
|
163
|
-
const info = await file.info();
|
|
164
|
-
// {
|
|
165
|
-
// id: "yOuZEdSsNLq8PgZyLhSz0Llh.jpg",
|
|
166
|
-
// type: "jpg",
|
|
167
|
-
// size: 435435,
|
|
168
|
-
// timestamp: "2024-08-07T14:26:37Z",
|
|
169
|
-
// url: "https://f???.backblazeb2.com/???/yOuZEdSsNLq8PgZyLhSz0Llh.jpg",
|
|
170
|
-
// }
|
|
171
|
-
|
|
172
|
-
return 200;
|
|
173
|
-
});
|
|
174
|
-
.;
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
#### Example: resizing the user profile picture
|
|
178
|
-
|
|
179
|
-
Let's see a complete example of uploading and resizing a user profile picture:
|
|
180
|
-
|
|
181
|
-
```js
|
|
182
|
-
import server, { status } from "@server/next";
|
|
183
|
-
import sharp from "sharp";
|
|
184
|
-
import FileSystem from "bucket/fs";
|
|
185
|
-
|
|
186
|
-
const uploads = FileSystem("./uploads");
|
|
187
|
-
|
|
188
|
-
// All paths are relative to the CWD
|
|
189
|
-
export default server({ uploads })
|
|
190
|
-
.get("/", () => "Hello")
|
|
191
|
-
.put("/users/:id", async (ctx) => {
|
|
192
|
-
// Create the instance of the file to read and write
|
|
193
|
-
const src = ctx.uploads.file(ctx.body.profile);
|
|
194
|
-
const dst = ctx.uploads.file("/profile/" + ctx.url.params.id + ".jpg");
|
|
195
|
-
|
|
196
|
-
const ext = src.id.split(".").pop();
|
|
197
|
-
if (!["jpg", "jpeg", "png", "webp", "avif"].includes(ext)) {
|
|
198
|
-
await src.remove(); // Don't store it
|
|
199
|
-
return status(400).json({ error: "Invalid file format" });
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Create the Readable, Transform and Writable Node streams
|
|
203
|
-
await pipeline(
|
|
204
|
-
src.readable("node"),
|
|
205
|
-
sharp().resize(200, 200).jpg(),
|
|
206
|
-
dst.writable("node")
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
// We no longer need the original file
|
|
210
|
-
await src.remove();
|
|
211
|
-
|
|
212
|
-
return status(200).json({ updated: true });
|
|
213
|
-
});
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Note that the option `uploads` gets converted into a `Bucket` instance and passed as `ctx.uploads`. Th
|
|
217
|
-
|
|
218
|
-
## Options
|
|
219
|
-
|
|
220
|
-
Options docs here
|
|
221
|
-
|
|
222
|
-
## Router
|
|
223
|
-
|
|
224
|
-
Router docs here
|
|
225
|
-
|
|
226
|
-
## Context
|
|
227
|
-
|
|
228
|
-
Context docs here
|
|
229
|
-
|
|
230
|
-
## Reply
|
|
231
|
-
|
|
232
|
-
Reply docs here
|
|
233
|
-
|
|
234
|
-
## Runtimes
|
|
235
|
-
|
|
236
|
-
There are many runtimes where Server works! We put a lot of work to make sure it works the same way with minimal changes in them, this includes:
|
|
237
|
-
|
|
238
|
-
- Node.js of course, including everywhere that Node is supported (VPS, Heroku, Render, etc).
|
|
239
|
-
- Bun, and it will be even faster!
|
|
240
|
-
- Cloudflare Worker
|
|
241
|
-
- Netlify Function + Edge function
|
|
242
|
-
|
|
243
|
-
## FAQ
|
|
244
|
-
|
|
245
|
-
#### How is it different from Hono?
|
|
246
|
-
|
|
247
|
-
Server.js attempts to run your code unmodified in all runtimes. With Hono, despite the claims in their homepage, you need to change the code for different runtimes:
|
|
248
|
-
|
|
249
|
-
```js
|
|
250
|
-
// Server.js code for Node.js, Bun and Netlify
|
|
251
|
-
import server from "@server/next";
|
|
252
|
-
export default server().get("/", () => "Hello server!");
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
```js
|
|
256
|
-
// Hono code for Node.js
|
|
257
|
-
import { serve } from '@hono/node-server'
|
|
258
|
-
import { Hono } from 'hono'
|
|
259
|
-
const app = new Hono()
|
|
260
|
-
app.get('/', (c) => c.text('Hello Node.js!'))
|
|
261
|
-
serve(app)
|
|
262
|
-
|
|
263
|
-
// Hono code for Bun
|
|
264
|
-
import { Hono } from 'hono'
|
|
265
|
-
const app = new Hono()
|
|
266
|
-
app.get('/', (c) => c.text('Hello Bun!'))
|
|
267
|
-
export default app
|
|
268
|
-
|
|
269
|
-
// Hono code for Netlify
|
|
270
|
-
import { Hono } from 'jsr:@hono/hono'
|
|
271
|
-
import { handle } from 'jsr:@hono/hono/netlify'
|
|
272
|
-
const app = new Hono()
|
|
273
|
-
app.get('/', (c) => c.text('Hello Hono!'))
|
|
274
|
-
export default handle(app)
|
|
275
|
-
```
|