Pogo
Server framework for Deno
Install / Use
/learn @sholladay/PogoREADME
pogo

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.20.0 and higher.
Contents
Why?
- Designed to encourage reliable and testable applications.
- Routes are simple, pure functions that return values directly.
- Automatic JSON responses from objects.
- Built-in support for React and JSX.
Usage
Save the code below to a file named server.js and 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. To make the server publicly accessible from other machines, add hostname : '0.0.0.0' to the options.
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.
Adding routes
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!' });
Serve static files
Using h.directory() (recommended)
You can use 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');
});
Using h.file()
You can use h.file() to send a specific file. It will read the file, wrap the contents in a Response, and automatically set the correct Content-Type header. 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');
});
Using byte arrays, streams, etc.
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 file = await Deno.open('./dogs.jpg');
return h.response(file).type('image/jpeg');
});
💡 Tip: Pogo automatically cleans up the resource (i.e. closes the file descriptor) when the response is sent. So you do not have to call Deno.close()! 🙂
React and JSX support
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.jsx and run it with a command like deno --allow-net server.jsx. The .jsx extension is important, as it tells Deno to compile the JSX syntax. You can also use TypeScript by using .tsx instead of .jsx.
import React from 'https://esm.sh/react';
import pogo from 'https://deno.land/x/pogo/main.ts';
const server = pogo.server({ port : 3000 });
server.router.get('/', () => {
return <h1>Hello, world!</h1>;
});
server.start();
Writing tests
Pogo is designed to make testing easy. When you write tests for your app, you will probably want to test your server and route handlers in some way. Pogo encourages pure functional route handlers, enabling them to be tested in isolation from each other and even independently of Pogo itself, with little to no mocking required.
If you want to go further and test the full request lifecycle, you can make actual fetch() requests to the server and assert that the responses have the values you expect. Pogo makes this style of testing easier with server.inject(), which is similar to fetch() except it bypasses the network layer. By injecting a request into the server directly, we can completely avoid the need to find an available port, listen on that port, make HTTP connections, and all of the problems and complexity that arise from networked tests. You should focus on writing your application logic and server.inject() makes that easier. It also makes your tests faster.
When using server.inject(), 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.
import pogo from 'https://deno.land/x/pogo/main.ts';
import { assertStrictEquals } from 'https://deno.land/std/testing/asserts.ts';
const { test } = Deno;
test('my app works', async () => {
const server = pogo.server();
server.router.get('/', () => {
return 'Hello, World!';
});
const response = await server.inject({
method : 'GET',
url : '/'
});
assertStrictEquals(response.status, 200);
assertStrictEquals(response.headers.get('content-type'), 'text/html; charset=utf-8');
assertStrictEquals(await response.text(), 'Hello, World!');
});
API
pogo.server(options)pogo.router(options?)- Server
- Request
- Response
response.bodyresponse.code(statusCode)response.created(url?)response.header(name, value)response.headersresponse.location(url)response.permanent()response.redirect(url)- [
response.rewritable(isRewritable?)](#responserewritableisrew
