Server framework for Deno
Server framework for Deno
Pogo is an easy-to-use, safe, and expressive framework for writing web servers and applications. It is inspired by hapi.
Supports Deno v1.2.0 and higher.
Save the code below to a file named
server.jsand run it with a command like
deno run --allow-net server.js. Then visit http://localhost:3000 in your browser and you should see "Hello, world!" on the page.
import pogo from 'https://deno.land/x/pogo/main.ts';const server = pogo.server({ port : 3000 });
server.router.get('/', () => { return 'Hello, world!'; });
server.start();
The examples that follow will build on this to add more capabilities to the server. Some advanced features may require additional permission flags or different file extensions. If you get stuck or need more concrete examples, be sure to check out the example projects.
A route matches an incoming request to a handler function that creates a response
Adding routes is easy, just call
server.route()and pass it a single route or an array of routes. You can call
server.route()multiple times. You can even chain calls to
server.route(), because it returns the server instance.
Add routes in any order you want to, it's safe! Pogo orders them internally by specificity, such that their order of precedence is stable and predictable and avoids ambiguity or conflicts.
server.route({ method : 'GET', path : '/hi', handler : () => 'Hello!' }); server.route({ method : 'GET', path : '/bye', handler : () => 'Goodbye!' });
server .route({ method : 'GET', path : '/hi', handler : () => 'Hello!' }); .route({ method : 'GET', path : '/bye', handler : () => 'Goodbye!' });
server.route([ { method : 'GET', path : '/hi', handler : () => 'Hello!' }, { method : 'GET', path : '/bye', handler : () => 'Goodbye!' } ]);
You can also configure the route to handle multiple methods by using an array, or
'*'to handle all possible methods.
server.route({ method : ['GET', 'POST'], path : '/hi', handler : () => 'Hello!' });
server.route({ method : '*', path : '/hi', handler : () => 'Hello!' });
h.directory()(recommended)
h.directory()to send any file within a directory based on the request path.
server.router.get('/movies/{file*}', (request, h) => { return h.directory('movies'); });
h.file()
h.file()to send a specific file. It will read the file, wrap the contents in a
Response, and automatically set the correct
Content-Typeheader. It also has a security feature that prevents path traversal attacks, so it is safe to set the path dynamically (e.g. based on the request URL).
server.router.get('/', (request, h) => { return h.file('dogs.jpg'); });
If you need more control over how the file is read, there are also more low level ways to send a file, as shown below. However, you'll need to set the content type manually. Also, be sure to not set the path based on an untrusted source, otherwise you may create a path traversal vulnerability. As always, but especially when using any of these low level approaches, we strongly recommend setting Deno's read permission to a particular file or directory, e.g.
--allow-read='.', to limit the risk of such attacks.
Using
Deno.readFile()to get the data as an array of bytes:
server.router.get('/', async (request, h) => { const buffer = await Deno.readFile('./dogs.jpg'); return h.response(buffer).type('image/jpeg'); });
Using
Deno.open()to get the data as a stream to improve latency and memory usage:
server.router.get('/', async (request, h) => { const stream = await Deno.open('./dogs.jpg'); return h.response(stream).type('image/jpeg'); });
JSX is a shorthand syntax for JavaScript that looks like HTML and is useful for constructing web pages
You can do webpage templating with React inside of route handlers, using either JSX or
React.createElement().
Pogo automatically renders React elements using
ReactDOMServer.renderToStaticMarkup()and sends the response as HTML.
Save the code below to a file named
server.jsxand run it with a command like
deno --allow-net server.jsx. The
.jsxextension is important, as it tells Deno to compile the JSX syntax. You can also use TypeScript by using
.tsxinstead of
.jsx, in which case you should add an
// @deno-typescomment to load the type definitions for React (see deno_types).
import React from 'https://jspm.dev/react'; import pogo from 'https://deno.land/x/pogo/main.ts';const server = pogo.server({ port : 3000 });
server.router.get('/', () => { return
Hello, world!
; });server.start();
When it comes time to write tests for your app, Pogo has you covered with
server.inject().
By injecting a request into the server directly, we can completely avoid the need to listen on an available port, make HTTP connections, and all of the problems and complexity that come along with that. You should focus on writing your application logic and
server.inject()makes that easier.
The server still processes the request using the same code paths that a normal HTTP request goes through, so you can rest assured that your tests are meaningful and realistic.
const response = await server.inject({ method : 'GET', url : '/users' });
pogo.server(options)
pogo.router(options?)
response.body
response.code(statusCode)
response.created(url?)
response.header(name, value)
response.headers
response.location(url)
response.permanent()
response.redirect(url)
response.rewritable(isRewritable?)
response.state(name, value)
response.status
response.temporary()
response.type(mediaType)
response.unstate(name)
router.add(route, options?, handler?)
router.all(route, options?, handler?)
router.delete(route, options?, handler?)
router.get(route, options?, handler?)
router.lookup(method, path)
router.patch(route, options?, handler?)
router.post(route, options?, handler?)
router.put(route, options?, handler?)
router.routes
Serverinstance, which can then be used to add routes and start listening for requests.
const server = pogo.server();
Type:
object
Type:
function
Optional route handler to be used as a fallback for requests that do not match any other route. This overrides the default 404 Not Found behavior built into the framework. Shortcut for
server.router.all('/{catchAll*}', catchAll).
const server = pogo.server({ catchAll(request, h) { return h.response('the void').code(404); } });
Type:
string\ Example:
'/path/to/file.cert'
Optional filepath to an X.509 public key certificate for the server to read when
server.start()is called, in order to set up HTTPS. Requires the use of the
keyFileoption.
Type:
string\ Default:
'localhost'
Optional domain or IP address for the server to listen on when
server.start()is called. Use
'0.0.0.0'to listen on all available addresses, as mentioned in the security documentation.
Type:
string\ Example:
'/path/to/file.key'
Optional filepath to a private key for the server to read when
server.start()is called, in order to set up HTTPS. Requires the use of the
certFileoption.
Type:
number\ Example:
3000
Any valid port number (
0to
65535) for the server to listen on when
server.start()is called. Use
0to listen on an available port assigned by the operating system.
Routerinstance, which can then be used to add routes.
const router = pogo.router();
The
serverobject returned by
pogo.server()represents your web server. When you start the server, it begins listening for HTTP requests, processes those requests when they are received, and makes the content within each request available to the route handlers that you specify.
Performs a request directly to the server without using the network. Useful when writing tests, to avoid conflicts from multiple servers trying to listen on the same port number.
Returns a
Promisefor a
Responseinstance.
const response = await server.inject({ method : 'GET', url : '/' });
Type:
object
Type:
string\ Example:
'GET'
Any valid HTTP method, such as
GETor
POST. Used to lookup the route handler.
Type:
string\ Example:
'/'
Any valid URL path. Used to lookup the route handler.
Adds a route to the server so that the server knows how to respond to requests that match the given HTTP method and URL path. Shortcut for
server.router.add().
Returns the server so other methods can be chained.
server.route({ method : 'GET', path : '/', handler : () => 'Hello, World!' });
server.route({ method : 'GET', path : '/' }, () => 'Hello, World!');
server.route('/', { method : 'GET' }, () => 'Hello, World!');
Type:
object|
string|
Router|
Array
Type:
string|
Array\ Example:
'GET'
Any valid HTTP method, array of methods, or
'*'to match all methods. Used to limit which requests will trigger the route handler.
Type:
string\ Example:
'/users/{userId}'
Any valid URL path. Used to limit which requests will trigger the route handler.
Supports path parameters with dynamic values, which can be accessed in the handler as
request.params.
Type:
function
requestis a
Requestinstance with properties for
headers,
method,
url, and more.
his a Response Toolkit instance, which has utility methods for modifying the response.
The implementation for the route that handles requests. Called when a request is received that matches the
methodand
pathspecified in the route options.
The handler must return one of: - A
string, which will be sent as HTML. - An
object, which will be stringified and sent as JSON. - A
Uint8Array, which will be sent as-is (raw bytes). - A
Response, which will send the
response.body, if any. - Any object that implements the
Readerinterface, such as a
Fileor
Buffer. - An
Error, which will send an appropriate HTTP error code - returning an error is the same as
throwing it. - A
Promisefor any of the above types.
Content-Typeheader will be set automatically based on the response body before the response is sent. You can use
response.type()to override the default behavior.
Router
The route manager for the server, which contains the routing table for all known routes, as well as various methods for adding routes to the routing table.
Begins listening for requests on the
hostnameand
portspecified in the server options.
Returns a
Promisethat resolves when the server is listening.
await server.start(); console.log('Listening for requests');
Stops accepting new requests. Any existing requests that are being processed will not be interrupted.
Returns a
Promisethat resolves when the server has stopped listening.
await server.stop(); console.log('Stopped listening for requests');
The
requestobject passed to route handlers represents an HTTP request that was sent to the server. It is similar to an instance of Deno's
ServerRequestclass, with some additions.
It provides properties and methods for inspecting the content of the request.
Reader
The HTTP body value.
To get the body as a string, pass it to
Deno.readAll()and decode the result, as shown below. Note that doing so will cause the entire body to be read into memory all at once, which is convenient and fine in most cases, but may be inappropriate for requests with a very large body.
server.router.post('/users', async (request) => { const bodyText = new TextDecoder().decode(await Deno.readAll(request.body)); const user = JSON.parse(bodyText); // ... });
If you want more control over how the stream is processed, instead of reading it all into memory, you can read raw bytes from the body in chunks with
request.body.read(). It takes a
Uint8Arrayas an argument to copy the bytes into and returns a
Promisefor either the number of bytes read or
nullwhen the body is finished being read. In the example below, we read up to a maximum of 20 bytes from the body.
server.router.post('/data', (request) => { const buffer = new Uint8Array(20); const numBytesRead = await request.body.read(buffer); const data = new TextDecoder().decode(buffer.subarray(0, numBytesRead)); // ... });
Headers
Contains the HTTP headers that were sent in the request, such as
Accept,
User-Agent, and others.
Type:
string\ Example:
'localhost:3000'
Hostheader, which is a combination of the hostname and port at which the server received the request, separated by a
:colon. Useful for returning different content depending on which URL your visitors use to access the server. Shortcut for
request.url.host.
To get the hostname, which does not include the port number, see
request.hostname.
Type:
string\ Example:
'localhost'
Hostheader. That is, the domain or IP address at which the server received the request, without the port number. Useful for returning different content depending on which URL your visitors use to access the server. Shortcut for
request.url.hostname.
To get the host, which includes the port number, see
request.host.
Type:
string\ Example:
'http://localhost:3000/page.html?query'
The full URL associated with the request, represented as a string. Shortcut for
request.url.href.
To get this value as a parsed object instead, use
request.url.
Type:
string\ Example:
'GET'
The HTTP method associated with the request, such as
GETor
POST.
Type:
string\ Example:
'http://localhost:3000'
The scheme and host parts of the request URL. Shortcut for
request.url.origin.
Type:
object
Contains the name/value pairs of path parameters, where each key is a parameter name from the route path and the value is the corresponding part of the request path. Shortcut for
request.route.params.
Type:
string\ Example:
/page.html
The path part of the request URL, excluding the query. Shortcut for
request.url.pathname.
ServerRequest
The original request object from Deno's
httpmodule, upon which many of the other request properties are based.
You probably don't need this. It is provided as an escape hatch, but using it is not recommended.
Type:
string
Refererheader, which is useful for determining where the request came from. However, not all user agents send a referrer and the value can be influenced by various mechanisms, such as
Referrer-Policy. As such, it is recommended to use the referrer as a hint, rather than relying on it directly.
Note that this property uses the correct spelling of "referrer", unlike the header. It will be an empty string if the header is missing.
Response
The response that will be sent for the request. To create a new response, see
h.response().
Type:
object
The route that is handling the request, as given to
server.route(), with the following additional properties: -
paramNamesis an array of path parameter names -
paramsis an object with properties for each path parameter, where the key is the parameter name, and the value is the corresponding part of the request path -
segmentsis an array of path parts, as in the values separated by
/slashes in the route path
Type:
string\ Example:
'?query'
The query part of the request URL, represented as a string. Shortcut for
request.url.search.
To get this value as a parsed object instead, use
request.searchParams.
URLSearchParams
The query part of the request URL, represented as an object that has methods for working with the query parameters. Shortcut for
request.url.searchParams.
To get this value as a string instead, use
request.search.
Server
The server that is handling the request.
Type:
object
Contains the name/value pairs of the HTTP
Cookieheader, which is useful for keeping track of state across requests, e.g. to keep a user logged in.
URL
The full URL associated with the request, represented as an object that contains properties for various parts of the URL,
To get this value as a string instead, use
request.href. In some cases, the URL object itself can be used as if it were a string, because it has a smart
.toString()method.
The
responseobject represents an HTTP response to the associated
requestthat is passed to route handlers. You can access it as
request.responseor create a new response with the Response Toolkit by calling
h.response(). It has utility methods that make it easy to modify the headers, status code, and other attributes.
Type:
string|
object|
Uint8Array|
Reader
The body that will be sent in the response. Can be updated by returning a value from the route handler or by creating a new response with
h.response()and giving it a value.
Sets the response status code. When possible, it is better to use a more specific method instead, such as
response.created()or
response.redirect().
Returns the response so other methods can be chained.
statusconstants to define the status code.
import { Status as status } from 'https://deno.land/std/http/http_status.ts'; const handler = (request, h) => { return h.response().code(status.Teapot); };
201 Createdand sets the
Locationheader to the value of
url, if provided.
Returns the response so other methods can be chained.
Sets a response header. Always replaces any existing header of the same name. Headers are case insensitive.
Returns the response so other methods can be chained.
Headers
Contains the HTTP headers that will be sent in the response, such as
Location,
Vary, and others.
Locationheader on the response to the value of
url. When possible, it is better to use a more specific method instead, such as
response.created()or
response.redirect().
Returns the response so other methods can be chained.
Only available after calling the
response.redirect()method.
301 Moved Permanentlyor
308 Permanent Redirectbased on whether the existing status is considered rewritable (see "method handling" on Redirections in HTTP for details).
Returns the response so other methods can be chained.
302 Foundand sets the
Locationheader to the value of
url.
Also causes some new response methods to become available for customizing the redirect behavior:
Returns the response so other methods can be chained.
Only available after calling the
response.redirect()method.
301 Moved Permanentlyor
302 Foundbased on whether the existing status is a permanent or temporary redirect code. If
isRewritableis
false, then the response status will be set to
307 Temporary Redirector
308 Permanent Redirect.
Returns the response so other methods can be chained.
Set-Cookieheader to create a cookie with the given
nameand
value. Cookie options can be specified by using an object for
value. See Deno's cookie interface for the available options.
Returns the response so other methods can be chained.
All of the following forms are supported:
response.state('color', 'blue'); response.state('color', { value : 'blue' }); response.state({ name : 'color', value : 'blue' });
Type:
number\ Example:
418
The status code that will be sent in the response. Defaults to
200, which means the request succeeded. 4xx and 5xx codes indicate an error.
Only available after calling the
response.redirect()method.
302 Foundor
307 Temporary Redirectbased on whether the existing status is considered rewritable (see "method handling" on Redirections in HTTP for details).
Returns the response so other methods can be chained.
Content-Typeheader on the response to the value of
mediaType.
Overrides the media type that is set automatically by the framework.
Returns the response so other methods can be chained.
Set-Cookieheader to clear the cookie given by
name.
Returns the response so other methods can be chained.
The response toolkit is an object that is passed to route handlers, with utility methods that make it easy to modify the response. For example, you can use it to set headers or a status code.
By convention, this object is assigned to a variable named
hin code examples.
Creates a new response with a body containing the contents of the directory or file specified by
path.
Returns a
Promisefor the response.
server.router.get('/movies/{file*}', (request, h) => { return h.directory('movies'); });
The directory or file that is served is determined by joining the path given to
h.directory()with the value of the last path parameter of the route, if any. This allows you to control whether the directory root or files within it will be accessible, by using a particular type of path parameter or lack thereof.
path: '/movies'will only serve the directory itself, meaning it will only work if the
listingoption is enabled (or if the path given to
h.directory()is actually a file instead of a directory), otherwise a
403 Forbiddenerror will be thrown.
path: '/movies/{file}'will only serve the directory's children, meaning that a request to
/movies/will return a
404 Not Found, even if the
listingoption is enabled.
path: '/movies/{file?}'will serve the directory itself and the directory's children, but not any of the directory's grandchildren or deeper descendants.
path: '/movies/{file*}'will serve the directory itself and any of the directory's descendants, including children and granchildren.
Note that the name of the path parameter (
filein the example above) does not matter, it can be anything, and the name itself won't affect the directory helper or the response in any way. You should consider it a form of documentation and choose a name that is appropriate and intuitive for your use case. By convention, we usually name it
file.
Type:
object
Type:
boolean\ Default:
false
If
true, enables directory listings, so that when the request path matches a directory (as opposed to a file), the response will be an HTML page that shows some info about the directory's children. including file names, file sizes, and timestamps for when the files were created and modified.
By default, directory listings are disabled for improved privacy, and instead a
403 Forbiddenerror will be thrown when the request matches a directory.
Note that this option does not affect which files within the directory are accessible. For example, with a route of
/movies/{file*}and
listing: false, the user could still access
/movies/secret.movif they knew (or were able to guess) that such a file exists. Conversely, with a route of
/moviesand
listing: true, the user would be unable to access
/movies/secret.movor see its contents, but they could see that it exists in the directory listing.
To control which files are accessible, you can change the route path parameter or use
h.file()to serve specific files.
Creates a new response with a body containing the contents of the file specified by
path. Automatically sets the
Content-Typeheader based on the file extension.
Returns a
Promisefor the response.
server.router.get('/', (request, h) => { return h.file('index.html'); });
Type:
object
Type:
boolean|
string\ Default:
Deno.cwd()(current working directory)
Optional directory path used to limit which files are allowed to be accessed, which is important in case the file path comes from an untrusted source, such as the request URL. Any file inside of the
confinedirectory will be accessible, but attempting to access any file outside of the
confinedirectory will throw a
403 Forbiddenerror. Set to
falseto disable this security feature.
Creates a new response with a redirect status. Shortcut for
h.response().redirect(url). See
response.redirect()for details.
Returns the response so other methods can be chained.
Creates a new response with an optional body. This is the same as returning the body directly from the route handler, but it is useful in order to begin a chain with other response methods.
Returns the response so other methods can be chained.
Documentation: Routing
A router is used to store and lookup routes. The server has a built-in router at
server.router, which it uses to match an incoming request to a route handler function that generates a response. You can use the server's router directly or you can create a custom router with
pogo.router().
To copy routes from one router to another, see
router.add(). You can pass a custom router to
server.route()or
server.router.add()to copy its routes into the server's built-in router, thus making those routes available to incoming requests.
Note that you don't necessarily need to create a custom router. You only need to create your own router if you prefer the chaining syntax for defining routes and you want to export the routes from a file that doesn't have access to the server. In other words, a custom router is useful for larger applications.
const server = pogo.server(); server.router .get('/', () => { return 'Hello, World!'; }) .get('/status', () => { return 'Everything is swell!'; });
const router = pogo.router() .get('/', () => { return 'Hello, World!'; }) .get('/status', () => { return 'Everything is swell!'; });const server = pogo.server(); server.route(router);
Adds one or more routes to the routing table, which makes them available for lookup, e.g. by a server trying to match an incoming request to a handler function.
The
routeargument can be: - A route object with optional properties for
method,
path, and
handler-
methodis an HTTP method string or array of strings -
pathis a URL path string -
handleris a function - A string, where it will be used as the path - A
Routerinstance, where its routing table will be copied - An array of the above types
The
optionsargument can be a route object (same as
route) or a function, where it will be used as the handler.
The
handlerfunction can be a property of a
routeobject, a property of the
optionsobject, or it can be a standalone argument.
Each argument has higher precedence than the previous argument, allowing you to pass in a route but override its handler, for example, by simply passing a handler as the final argument.
Returns the router so other methods can be chained.
const router = pogo.router().add('/', { method : '*' }, () => 'Hello, World!');
router.add(), with
'*'as the default HTTP method.
Returns the router so other methods can be chained.
const router = pogo.router().all('/', () => 'Hello, World!');
router.add(), with
'DELETE'as the default HTTP method.
Returns the router so other methods can be chained.
const router = pogo.router().delete('/', () => 'Hello, World!');
router.add(), with
'GET'as the default HTTP method.
Returns the router so other methods can be chained.
const router = pogo.router().get('/', () => 'Hello, World!');
Look up a route that matches the given
methodand
path.
Returns the route object with an additional
paramsproperty that contains path parameter names and values.
router.add(), with
'PATCH'as the default HTTP method.
Returns the router so other methods can be chained.
const router = pogo.router().patch('/', () => 'Hello, World!');
router.add(), with
'POST'as the default HTTP method.
Returns the router so other methods can be chained.
const router = pogo.router().post('/', () => 'Hello, World!');
router.add(), with
'PUT'as the default HTTP method.
Returns the router so other methods can be chained.
const router = pogo.router().put('/', () => 'Hello, World!');
Type:
object
The routing table, which contains all of the routes that have been added to the router.
See our contributing guidelines for more details.
git checkout -b my-new-feature
git commit -am 'Add some feature'
git push origin my-new-feature
Go make something, dang it.