@server/next 0.20.15 → 0.20.16

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.20.15",
3
+ "version": "0.20.16",
4
4
  "description": "An experimental reimplementation of server.js focused on the DX",
5
5
  "homepage": "https://node-server.com/",
6
6
  "repository": "https://github.com/franciscop/server-next.git",
@@ -30,6 +30,7 @@
30
30
  },
31
31
  "engineStrict": true,
32
32
  "devDependencies": {
33
+ "argon2": "^0.40.3",
33
34
  "jest": "^29.7.0",
34
35
  "polystore": "^0.8.0"
35
36
  },
@@ -37,7 +38,5 @@
37
38
  "testEnvironment": "jest-environment-node",
38
39
  "transform": {}
39
40
  },
40
- "dependencies": {
41
- "argon2": "^0.40.3"
42
- }
41
+ "dependencies": {}
43
42
  }
package/readme.md CHANGED
@@ -1,116 +1,121 @@
1
- # Server @ Next
1
+ # Documentation
2
2
 
3
3
  > **⚠️ WIP** This is an **experimental library** right now!
4
4
 
5
- A fully-fledged web server for Bun and Node.js, with all the basics covered for you:
5
+ A fully-fledged web server for Bun and Node.js, with all the basics built-in:
6
6
 
7
7
  ```js
8
8
  import server from "@server/next";
9
9
 
10
- // Create a running instance of the server
11
10
  export default server(options)
12
11
  .get("/books", () => Book.list())
13
- .post("/books", { body: BookSchema }, (ctx) => {
12
+ .post("/books", BookSchema, (ctx) => {
14
13
  return Book.create(ctx.body).save();
15
14
  });
16
15
  ```
17
16
 
18
- 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. We also have integrations with these:
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.
19
18
 
20
- - KV Stores: in-memory, Redis, Consul, DynamoDB.
19
+ We also have integrations and adaptors for these:
20
+
21
+ - KV Stores: in-memory, Redis, Consul, DynamoDB, [Level](https://github.com/Level/level).
21
22
  - Buckets: AWS S3, Cloudflare R2, Backblaze B2.
22
- - Validation libraries: Zod, Joi, Yup.
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.
23
25
 
24
26
  ```js
25
- // Easy testing as well - index.test.js
27
+ // How to test your server - index.test.js
26
28
  import app from "./";
27
29
  const api = app.test(); // Very convenient helper, AXIOS-like interface
28
30
 
29
- it("can retrieve the homepage", async () => {
31
+ it("can retrieve the book list", async () => {
30
32
  const { data: books } = await api.get("/books/");
31
33
  expect(books[0]).toEqual({ id: 0, name: ... });
32
34
  });
33
35
  ```
34
36
 
35
- ## Upgrading server
37
+ ## Getting started
36
38
 
37
- Why? We live in the era of multi-cloud (Heroku, Workers, Lambda, etc) and multi-runtimes (Node.js, Bun, WinterGC, etc). Desired improvements (WIP!):
39
+ First install it:
38
40
 
39
- - Tiny footprint with few dependencies. Installing and using the full library takes under 10kb (target limit).
40
- - Faster! Reimplemented from scratch for speed. With raw ES6+ and a tiny code footprint, your server will fly.
41
- - Modern ES6+ESM syntax for both the library and examples.
42
- - Not using express underneath anymore. Considering keeping the compatibility layer anyway (since Express itself is a thin layer).
43
- - Changed the reply logic greatly, including the removal of `render()`. This is the main reason express was removed. Many servers don't need render() at all.
44
- - **[security]** Removed mandatory CSRF token, since this is only useful for server-rendered pages and not for SPA. Now we provide an `auth` module instead.
41
+ ```
42
+ npm install @server/next
43
+ yarn add @server/next
44
+ bun install @server/next
45
+ ```
45
46
 
46
- ## Progress
47
+ Now you can create your first simple server:
47
48
 
48
- - Router has all verbs, as well as URL pattern matches
49
- - Full URL parsing, including `query` and `params` in `ctx.url`.
50
- - Body and Files parsing is working (need testing)
51
- - The middleware can return:
52
- - A number and it'll be set as the status code
53
- - A string and it'll be sent as plain text or html (if it starts with "<")
54
- - A readStream and it'll be piped to the response
55
- - An object with `status`, `body` and `headers` and it'll be set raw.
56
- - Response compression works
57
- - Zod light integration
58
- - Auth work
49
+ ```js
50
+ // index.js
51
+ import server from "@server/next";
59
52
 
60
- ## Examples
53
+ export default server()
54
+ .get("/", () => "Hello world")
55
+ .post("/", (ctx) => {
56
+ console.log(ctx.body);
57
+ return 201;
58
+ });
59
+ ```
61
60
 
62
- ### Streams
61
+ Then run `node .` or `bun .` and open your browser on http://localhost:3000/ to see the message.
63
62
 
64
- Creating a 100x100px thumbnail on the fly with Sharp:
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.
65
64
 
66
- ```js
67
- // createThumbnail.js
68
- import sharp from "sharp";
65
+ ## Basic usage
69
66
 
70
- export default function createThumbnail(ctx) {
71
- // Return a pipe, which will be streamed to the output
72
- return sharp(ctx.url.params.name).resize(100, 100, { fit: "cover" }).png();
73
- }
74
- ```
67
+ Now that you know how to create a barebones server, there are some important bits that you might want to update.
75
68
 
76
- ### Breaking Changes
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.
77
70
 
78
- `import`, `export` and routing are the main changes from your point of view:
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.
79
72
 
80
- ```js
81
- import server, { status, type, ...reply } from 'server';
73
+ > Note: `bucket` is still _not_ available
82
74
 
83
- export default server({ ...options })
84
- .use(mid1)
85
- .get('/', cb1)
86
- .get('/b', mid2, cb2)
87
- .routes({ get: [['/c', mid3, cb3]] });
88
- ```
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.
89
76
 
90
- `status()` now it's always partial:
77
+ An example of how that works in practice:
91
78
 
92
79
  ```js
93
- // OLD
94
- return 404;
95
- return status(404).send("Not here..."); // treated as partial
96
- return status(404); // treated as final
97
-
98
- // NEW
99
- return 404;
100
- return status(404).send("Not here..."); // GOOD
101
- return status(404).send(); // GOOD
102
-
103
- // DON'T DO:
104
- return status(404); // INVALID
105
- ```
80
+ // index.js
81
+ import server from "@server/next";
106
82
 
107
- ```js
108
- import server from "server";
83
+ import Bucket from "bucket/b2";
84
+ import { createClient } from "redis";
109
85
 
110
- export default server()
86
+ const bucket = Bucket("mybucketname", { id, key });
87
+ const store = createClient("...").connect();
88
+
89
+ export default server({ bucket, store })
111
90
  .get("/", () => "Hello world")
112
91
  .post("/", (ctx) => {
113
92
  console.log(ctx.body);
114
93
  return 201;
115
94
  });
116
95
  ```
96
+
97
+ ## Guides
98
+
99
+ ### Middleware
100
+
101
+ ### Validation
102
+
103
+ ### Stores
104
+
105
+ ### File handling
106
+
107
+ ## Options
108
+
109
+ Options docs here
110
+
111
+ ## Router
112
+
113
+ Router docs here
114
+
115
+ ## Context
116
+
117
+ Context docs here
118
+
119
+ ## Reply
120
+
121
+ Reply docs here
@@ -1,10 +1,36 @@
1
- import argon2 from "argon2";
2
-
3
1
  import { createId } from "../../helpers/index.js";
4
2
  import { ServerError, status } from "../../index.js";
5
3
  import findUser from "../findUser.js";
6
4
  import updateUser from "../updateUser.js";
7
5
 
6
+ const hash = new Proxy(
7
+ {},
8
+ {
9
+ get: (self, key) => {
10
+ const load = async () =>
11
+ Object.assign(
12
+ self,
13
+ await import("argon2").catch(() => {
14
+ throw new ServerError.AUTH_ARGON_NEEDED();
15
+ })
16
+ );
17
+ if (key === "verify" && !self.verify) {
18
+ return async (hash, pass) => {
19
+ await load();
20
+ return self.verify(hash, pass);
21
+ };
22
+ }
23
+ if (key === "hash" && !self.hash) {
24
+ return async (pass) => {
25
+ await load();
26
+ return self.hash(pass);
27
+ };
28
+ }
29
+ return self[key];
30
+ },
31
+ }
32
+ );
33
+
8
34
  const createSession = async (user, ctx) => {
9
35
  const { type, session, cleanUser } = ctx.options.auth;
10
36
  user = cleanUser(user);
@@ -45,7 +71,7 @@ async function login(ctx) {
45
71
  if (!(await store.has(email))) throw ServerError.LOGIN_WRONG_EMAIL();
46
72
 
47
73
  const user = await store.get(email);
48
- const isValid = await argon2.verify(user.password, password);
74
+ const isValid = await hash.verify(user.password, password);
49
75
  if (!isValid) throw ServerError.LOGIN_WRONG_PASSWORD();
50
76
 
51
77
  return createSession(user, ctx);
@@ -64,7 +90,7 @@ async function register(ctx) {
64
90
  const user = {
65
91
  id: createId(),
66
92
  email,
67
- password: await argon2.hash(password),
93
+ password: await hash.hash(password),
68
94
  ...data,
69
95
  };
70
96
  await store.set(email, user);
@@ -99,10 +125,10 @@ async function password(ctx) {
99
125
 
100
126
  const fullUser = await findUser(ctx.auth, ctx.options.auth.store);
101
127
 
102
- const isValid = await argon2.verify(fullUser.password, previous);
128
+ const isValid = await hash.verify(fullUser.password, previous);
103
129
  if (!isValid) throw ServerError.LOGIN_WRONG_PASSWORD();
104
130
 
105
- fullUser.password = await argon2.hash(updated);
131
+ fullUser.password = await hash.hash(updated);
106
132
  await updateUser(fullUser, ctx.auth, ctx.options.auth.store);
107
133
 
108
134
  return 200;
@@ -4,6 +4,9 @@ ServerError.extend({
4
4
  NO_STORE: `You need a 'store' to write 'ctx.session'`,
5
5
  NO_STORE_WRITE: `You need a 'store' to write 'ctx.session.{key}'`,
6
6
  NO_STORE_READ: `You need a 'store' to read 'ctx.session.{key}'`,
7
+
8
+ AUTH_ARGON_NEEDED:
9
+ "Argon2 is needed for the auth module, please install it with 'npm i argon2'",
7
10
  AUTH_INVALID_TYPE: `Invalid Authorization type, '{type}'`,
8
11
  AUTH_INVALID_TOKEN: `Invalid Authorization token`,
9
12
  AUTH_INVALID_COOKIE: `Invalid Authorization cookie`,
@@ -18,14 +21,14 @@ ServerError.extend({
18
21
  LOGIN_NO_EMAIL: "The email is required to log in",
19
22
  LOGIN_INVALID_EMAIL: "The email you wrote is not correct",
20
23
  LOGIN_NO_PASSWORD: "The email is required to log in",
21
- LOGIN_INVALID_PASSWORD: "The email you wrote is not correct",
24
+ LOGIN_INVALID_PASSWORD: "The password you wrote is not correct",
22
25
  LOGIN_WRONG_ACCOUNT: `That email does not correspond to any account`,
23
26
  LOGIN_WRONG_PASSWORD: `That is not the valid password`,
24
27
 
25
28
  REGISTER_NO_EMAIL: `Email needed`,
26
29
  REGISTER_INVALID_EMAIL: "The email you wrote is not correct",
27
30
  REGISTER_NO_PASSWORD: `Password needed`,
28
- REGISTER_INVALID_PASSWORD: "The email you wrote is not correct",
31
+ REGISTER_INVALID_PASSWORD: "The password you wrote is not correct",
29
32
  REGISTER_EMAIL_EXISTS: `Email is already registered`,
30
33
  });
31
34