sleepy-serv 0.1.1 → 0.1.3

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.
Files changed (2) hide show
  1. package/README.md +271 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # Sleepy Server
2
+
3
+ A directory-driven web server designed for REST-ful applications
4
+
5
+ ## Important Notes
6
+
7
+ - This package requires [`bun.sh`](https://bun.sh) instead of NodeJS to run.
8
+ - This project requires bun v1.2.3 or higher
9
+
10
+ ## Getting Started
11
+
12
+ Here's a minimalist example on how to create a sleepy-serv app:
13
+
14
+ ```js
15
+ import {
16
+ middleware,
17
+ createApp,
18
+ } from 'sleepy-serv'
19
+
20
+ const PORT = 3000
21
+
22
+ const app = await createApp(PORT, import.meta.dirname)
23
+ ```
24
+
25
+ The parameter for `import.meta.dirname` can be any directory you prefer, but it's common to point to the same directory as your root `index.js` file. The next step is to create an `/api` directory in the directory that you point to, and begin adding routes.
26
+
27
+ ### Return Value
28
+
29
+ `sleepy-serv` was originally built for NodeJS, but it was ported to `bun` recently (before the initial release). The `createApp()` function merely calls `Bun.serve()` under-the-hood, and returns the `app` object that contains two properties:
30
+ - `routes`: Contains a list of all of the routes defined by the file structure. This is useful for debugging.
31
+ - `server`: this is the object that's returned from `Bun.serve()`. The `server` object has an `async` `.stop()` method on it, which can also be used for graceful shutdowns.
32
+
33
+ ### Adding Routes
34
+
35
+ Routes are made up as _resources_ and _methods_. Resources are described by a directory path, and methods are described by files created inside of those directories. Resource segments can also represent dynamic routing params by starting the directory name with a colon (`:`).
36
+
37
+ Here's file structure example:
38
+
39
+ ```
40
+ /src
41
+ index.js # this is where we called `createApp()`
42
+ /api
43
+ /users
44
+ get.js
45
+ post.js
46
+ /:userId
47
+ get.js
48
+ delete.js
49
+ put.js
50
+ ```
51
+
52
+ The file structure above create the following routes:
53
+
54
+ - `GET /users`
55
+ - `POST /users`
56
+ - `GET /users/:userId`
57
+ - `DELETE /users/:userId`
58
+ - `PUT /users/:userId`
59
+
60
+ These methods are supported:
61
+
62
+ - GET
63
+ - HEAD
64
+ - PATCH
65
+ - POST
66
+ - PUT
67
+ - DELETE
68
+
69
+ ### Method Definition Files
70
+
71
+ The route's logic is implemented in the route definition files. These must `export default` either a function, or an array of functions for middleware purposes. The function signature takes a `BunRequest` object, and must return a `BunResponse` object.
72
+
73
+ Here's an example of a simple handler function:
74
+
75
+ ```js
76
+ export default function (req) {
77
+ return Response('Hello world')
78
+ }
79
+ ```
80
+
81
+ These handlers can also be `async` functions and `sleepy-serv` will wait for them to finish before moving on.
82
+
83
+ ### Middleware
84
+
85
+ As mentioned earlier, method definition files can also export an array of functions:
86
+
87
+ ```js
88
+ export default [
89
+ async req => {
90
+ req.parsedBody = await req.json()
91
+ },
92
+ req => {
93
+ console.log('JSON body:', req.parsedBody)
94
+
95
+ return new Response('Hello world', )
96
+ },
97
+ ]
98
+ ```
99
+
100
+ This is useful if you want to break common logic up into reusable functions. The functions in the array are called in-order, and are provided the same `req` object. If you'd like to cache results between middleware functions, you can simply attach them to the `req` object.
101
+
102
+ ### Breaking the Middleware Chain - Responses
103
+
104
+ You might want to break the middleware chain with a response early.
105
+
106
+ Here's an example:
107
+
108
+ ```js
109
+ export default [
110
+ req => {
111
+ if (req.params.userId === '123') {
112
+ return new Response('Returned early')
113
+ }
114
+ },
115
+ req => {
116
+ return new Response('End of chain')
117
+ },
118
+ ]
119
+ ```
120
+
121
+ The the case above, the 1st middleware function will return a `Response` object if the route param's `userId` is equal to a specific value. In those cases, the last function in the middleware chain will not execute for that request.
122
+
123
+ ### Breaking the Middleware Chain - Errors
124
+
125
+ It's also common to throw errors for things like request validation or when a desired resource is not found. `sleepy-serv` has defined custom `Error` types for every type of 4xx and 5xx error:
126
+
127
+ ```js
128
+ import { NotFoundError } from 'sleepy-serv'
129
+
130
+ export default async function (req) {
131
+ const users = await sql`
132
+ SELECT * FROM Users
133
+ WHERE userId=${req.params.userId}
134
+ `.trim()
135
+
136
+ const foundUser = users[0]
137
+
138
+ if (foundUser) {
139
+ return new Response.json(foundUser)
140
+ } else {
141
+ throw new NotFoundError()
142
+ }
143
+ }
144
+ ```
145
+
146
+ If the user is found in the database, then the request will return with a successful response containing the user's data. If the user is not found, then the `NotFoundError()` is thrown which will automatically respond with a _404 NotFound_ error.
147
+
148
+ Throwing generic errors also works too:
149
+
150
+ ```js
151
+ export default async function (req) {
152
+ throw new Error('A problem occurred')
153
+ }
154
+ ```
155
+
156
+ `sleepy-serv` will automatically respond with a _500 InternalServerError_ for any error types that aren't part of the `sleepy-serv` package.
157
+
158
+ ## Metadata Modules
159
+
160
+ It's also possible for resource directories to contain a `meta.js` file. These files can export various things that have some sort semantic relationship to the part of the route that they're defined in.
161
+
162
+ ### Directory-Level Middleware
163
+
164
+ `meta.js` files can also export an array of middleware functions:
165
+
166
+ ```js
167
+ // meta.js
168
+
169
+ export const middleware = [
170
+ req => { /* do middleware things */ },
171
+ ]
172
+ ```
173
+
174
+ This middleware will be applied to all sibling and descendent method definition files within that directory.
175
+
176
+ For example:
177
+
178
+ ```
179
+ /src
180
+ index.js
181
+ /api
182
+ meta.js # metadata
183
+ get.js
184
+ /users
185
+ meta.js # metadata
186
+ get.js
187
+ post.js
188
+ /:userId
189
+ meta.js # metadata
190
+ get.js
191
+ delete.js
192
+ put.js
193
+ ```
194
+
195
+ The middleware defined in `/api/meta.js` will be applied the following routes:
196
+
197
+ - `GET /`
198
+ - `GET /users`
199
+ - `POST /users`
200
+ - `GET /users/:userId`
201
+ - `DELETE /users/:userId`
202
+ - `PUT /users/:userId`
203
+
204
+ The middleware defined in `/api/users/meta.js` will be applied the following routes:
205
+
206
+ - `GET /users`
207
+ - `POST /users`
208
+ - `GET /users/:userId`
209
+ - `DELETE /users/:userId`
210
+ - `PUT /users/:userId`
211
+
212
+ The middleware defined in `/api/users/:userId/meta.js` will be applied the following routes:
213
+
214
+ - `GET /users/:userId`
215
+ - `DELETE /users/:userId`
216
+ - `PUT /users/:userId`
217
+
218
+ ### Future Use-Cases
219
+
220
+ At the time of this writing, `meta.js` only exports middleware functions.
221
+
222
+ ## `createApp()` Options
223
+
224
+ ### `mountPath`
225
+
226
+ This adds a prefix to all routes. For example:
227
+
228
+ ```js
229
+ createApp(import.meta.dirname, {
230
+ mountPath: 'api/public',
231
+ })
232
+ ```
233
+
234
+ With the directory structure
235
+
236
+ ```
237
+ /src
238
+ index.js # createApp()
239
+ /api
240
+ get.js
241
+ /users
242
+ get.js
243
+ post.js
244
+ ```
245
+
246
+ Yields these routes:
247
+
248
+ - `GET /api/public`
249
+ - `GET /api/public/users`
250
+ - `POST /api/public/users`
251
+
252
+ ### `onClose`
253
+
254
+ When the app is started, the app can be shutdown gracefully by pressing Ctrl+D in the terminal. The `onClose` hook will be called during that shutdown if it's defined. `onClose` can also be `async` as well.
255
+
256
+ ```js
257
+ const app = await createApp(PORT, import.meta.dirname, {
258
+ onClose: () => console.info('closing down...'),
259
+ })
260
+ ```
261
+
262
+ ## Running the Local Example App
263
+
264
+ 1. Install [`bun.sh`](https://bun.sh)
265
+ 1. Link the library package:
266
+ - `$ cd lib`
267
+ - `$ bun link`
268
+ 1. Link the library to the project
269
+ - `$ cd ../example`
270
+ - `$ npm link sleepy-serv`
271
+ 1. Finally, run the app: `$ bun --watch run start`
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "sleepy-serv",
3
3
  "description": "A more opinionated web server for building REST-ful applications",
4
4
  "author": "Travis J True",
5
- "version": "0.1.1",
5
+ "version": "0.1.3",
6
6
  "exports": "./src/index.js",
7
7
  "type": "module",
8
8
  "keywords": [
@@ -23,4 +23,4 @@
23
23
  "ajv": "^8.17.1",
24
24
  "ajv-formats": "^3.0.1"
25
25
  }
26
- }
26
+ }