Precise
A clean and easy web server powered by Deno.
Getting started
Minimal example
demo/sample1.ts
:import WebServer from 'https://deno.land/x/precise/mod.ts' void new WebServer() .register({ path: '/', handler: () => Response.json({ foo: 'bar' }), }) .start()
Run the server:
$ deno run demo/sample1.ts 35:511 [Info ] Logging session initialized. Initial logger min log level: Debug (programmatically set) 35:511 [Info ] Create API server 35:511 [Info ] Register 'handler' with route GET / 35:512 [Info ] Start server β Granted env access to "PORT". β Granted net access to "0.0.0.0:8000". 37:869 [Info ] Apply server request handler 'handler' 37:869 [Info ] Apply server request handler 'defaultNotFoundRequestHandler' 37:869 [Info ] Accept connection 37:869 [Info ] Waiting for new connection 37:869 [Info ] Web server running. Access it at: http://localhost:8000/ $ β
Request:
$ http :8000 HTTP/1.1 200 OK content-length: 13 content-type: application/json date: Wed, 09 Nov 2022 07:02:46 GMT vary: Accept-Encoding { "foo": "bar" } $ β
Why
This project has been created because of the lack of a stop method in Http Deno and the others third party modules.
I wanted a simple web server service, that starts, registers, and stops, and donβt want to deal with 2 imbricated async iterator loops (serving-http).
Features
- Basic service lifecycle
- Route matching
- Route params
Logger & signals handling
demo/sample2.ts
:import WebServer, { exitOnSignals } from 'https://deno.land/x/precise/mod.ts' const webServer = new WebServer() webServer.register({ path: '/', handler: () => Response.json({ foo: 'bar' }), }) await webServer.start() exitOnSignals(webServer)
Run the server:
$ deno run demo/sample2.ts 55:933 [Info ] Logging session initialized. Initial logger min log level: Debug (programmatically set) 55:933 [Info ] Create API server 55:933 [Info ] Register 'handler' with route GET / 55:934 [Info ] Start server β Granted env access to "PORT". β Granted net access to "0.0.0.0:8000". 58:410 [Info ] Apply server request handler 'handler' 58:410 [Info ] Apply server request handler 'defaultNotFoundRequestHandler' 58:410 [Info ] Accept connection 58:410 [Info ] Waiting for new connection 58:410 [Info ] Web server running. Access it at: http://localhost:8000/ 58:410 [Debug ] Handling signal SIGINT 58:410 [Debug ] Handling signal SIGTERM 58:411 [Debug ] Handling signal SIGQUIT 58:411 [Info ] Type 'kill -s SIGINT 35054' to stop $ β
Stop the server:
$ kill -s SIGINT 35054 $ β
Server logs:
57:475 [Warn ] Received signal SIGINT 57:475 [Info ] Stop server 57:476 [Warn ] Listener has been closed 57:476 [Info ] End processing connection 57:476 [Info ] Logging session complete. Duration: 61543ms $ β
The server is properly stopped without the need to Deno.exit(), so that it can be used cleanly into end-to-end tests.
Route params
Handle routes parameters:
demo/sample3.ts
:import WebServer, { exitOnSignals } from 'https://deno.land/x/precise/mod.ts' const webServer = new WebServer() const { logger } = webServer webServer.register({ method: 'POST', path: '/execute/:cmd', handler: (req) => { if (req.params?.cmd === 'stop') { setTimeout(async () => { try { await webServer.stop() } catch (err) { logger.error(err) Deno.exit(1) } }, 1000) return new Response(undefined, { status: 202 }) } return Response.json({ foo: 'bar' }) }, }) try { await webServer.start() } catch (err) { logger.error(err) Deno.exit(1) } exitOnSignals(webServer)
Request:
$ http post :8000/execute/stop HTTP/1.1 202 Accepted content-length: 0 date: Wed, 09 Nov 2022 08:37:53 GMT vary: Accept-Encoding $ β
Server logs:
09 09:37:53:001 [Info ] Handle connection 09 09:37:53:001 [Info ] Waiting for new request in connection#8 09 09:37:53:002 [Info ] Waiting for new connection 09 09:37:53:002 [Info ] Handle request 09 09:37:53:003 [Debug ] Request pathname '/execute/stop' matches '/execute/:cmd': apply request handler 'handler' 09 09:37:53:003 [Info ] Waiting for new request in connection#8 09 09:37:53:006 [Debug ] No more request pending for connection#8 09 09:37:53:006 [Info ] End processing request in connection#8 09 09:37:54:005 [Info ] Stop server 09 09:37:54:005 [Warn ] Listener has been closed 09 09:37:54:005 [Info ] End processing connection 09 09:37:54:006 [Info ] Logging session complete. Duration: 5337ms $ β
Fallbacks
In case of a not found resource or an error, defaults handler are applied.
Feel free to use custom not found and error handlers.
demo/sample4.ts
:import WebServer from 'https://deno.land/x/precise/mod.ts' const webServer = new WebServer() webServer.register({ path: '/oops', handler: () => { throw new Error('oops') }, }) webServer.setNotFoundHandler((req) => Response.json( { code: 'NOT_FOUND', message: `Resource '${req.method} ${req.url}' not found.`, }, { status: 404 }, ), ) webServer.setErrorHandler((req: Request, err: Error, responseSent: boolean) => { if (responseSent) { return } return Response.json( { code: 'INTERNAL_SERVER', message: `Error encountered in request '${req.method} ${req.url}': ${err.message}.`, }, { status: 500 }, ) }) void webServer.start()
Request:
$ http :8000/myverybadroute HTTP/1.1 404 Not Found content-encoding: gzip content-length: 112 content-type: application/json date: Wed, 09 Nov 2022 15:49:01 GMT vary: Accept-Encoding { "code": "NOT_FOUND", "message": "Resource 'GET http://localhost:8000/myverybadroute' not found." } $ http :8000/oops HTTP/1.1 500 Internal Server Error content-encoding: gzip content-length: 123 content-type: application/json date: Wed, 09 Nov 2022 15:50:11 GMT vary: Accept-Encoding { "code": "INTERNAL_SERVER", "message": "Error encountered in request 'GET http://localhost:8000/oops': oops." } $ β
Server logs:
09 09:37:53:001 [Info ] Handle connection 09 09:37:53:001 [Info ] Waiting for new request in connection#8 09 09:37:53:002 [Info ] Waiting for new connection 09 09:37:53:002 [Info ] Handle request 09 09:37:53:003 [Debug ] Request pathname '/execute/stop' matches '/execute/:cmd': apply request handler 'handler' 09 09:37:53:003 [Info ] Waiting for new request in connection#8 09 09:37:53:006 [Debug ] No more request pending for connection#8 09 09:37:53:006 [Info ] End processing request in connection#8 09 09:37:54:005 [Info ] Stop server 09 09:37:54:005 [Warn ] Listener has been closed 09 09:37:54:005 [Info ] End processing connection 09 09:37:54:006 [Info ] Logging session complete. Duration: 5337ms $ β
Or in a simpler all-in-one form:
import WebServer from 'https://deno.land/x/precise/mod.ts' void new WebServer({ errorHandler: (req: Request, err: Error, responseSent: boolean) => { if (responseSent) { return } return Response.json( { code: 'INTERNAL_SERVER', message: `Error encountered in request '${req.method} ${req.url}': ${err.message}.`, }, { status: 500 }, ) }, notFoundHandler: (req: Request) => Response.json( { code: 'NOT_FOUND', message: `Resource '${req.method} ${req.url}' not found.`, }, { status: 404 }, ), requestHandlerSpecs: [ { path: '/oops', handler: () => { throw new Error('oops') }, }, ], }).start()
License
The MIT License