DEV Community

Cover image for Introduction to Tino - tiny and functional HTTP server for Deno
Marko Jakic
Marko Jakic

Posted on • Updated on

Introduction to Tino - tiny and functional HTTP server for Deno

Little bit of theory

Since Deno released its version 1.0 earlier this year I was eager to make an HTTP server for it. And, few weeks later I had some basic routing... thing, I called Tino. It's tiny, it's for Deno.

Tino also offers local JSON REST API for rapid prototyping via responders. More on that when you scroll down.

Keep in mind, it is only JavaScript for now, so no TypeScript, but, that doesn't matter really since we can run both in Deno. We're waiting for this issue to become ready and TS 4.0 version bundled into Deno to make Tino fully TS, too. Why? Because variadic tuples are not yet supported and Tino uses function composition heavily.

I didn't want to have it "java-esque" behaviour with decorated routes and classes and what not (think of Spring Boot for example) only because it is now possible to write TypeScript out of the box. Not saying it's a bad thing, on the contrary.

On the other hand Tino uses only functions and (async) function composition. Async composition (for middlewares) is necessary so you can compose both sync and async functions.

Usage and examples

Let's see how it actually works and how much freedom one might have using functions only. (Take a look at examples any time)

First steps

Import Tino from third party modules (for version number look at

import tino from "";
const app = tino.create();
// ... you business logic
tino.listen({ app, port: 8000 });
console.log(`Server running at 8000`);
Enter fullscreen mode Exit fullscreen mode

Now we can focus on the rest of your logic, i.e. defining endpoints, controllers and responders.

Defining your first endpoint is as easy as:

const use = () => ({ resp: "pong" });
app.get(() => ({ path: "/ping", use }));

// Even shorter, but only for prototyping:
app.get(() => ({ path: "/ping", resp: "pong" }));
Enter fullscreen mode Exit fullscreen mode

use is your controller. It is flexible and used also for extending Tino's functionality and custom attributes. Read on for more on that.

Both use and resp can be functions, but it makes more sense for use - if resp is a function, it can still receive props but will be called internally and its return will be used as return for use. ;)

Let's see what controller(use) can be and what it can return:

// A function or async function, only returning a string (can be any primitive)
// content-type: text/plain
const returnPong = ({ resp: "pong" })
const use1 = () => returnPong;
const use2 = async () => returnPong;
app.get(() => ({ path: "/ping", use: use1 }));

// Return an object:
// content-type: application/json
const use = () => ({ resp: () => ({}) });

// Set type and status
const use = () => ({ resp: () => "pong", status: 201, type: "text/plain" });
Enter fullscreen mode Exit fullscreen mode

Named params

Tino uses named params for defining your routes, like:

app.get(() => ({ path: "/users/:id", use }));
Enter fullscreen mode Exit fullscreen mode


Any controller can receive props:

const use = (props) => ({ resp: }); => ({ path: "/users/:id", use }));
Enter fullscreen mode Exit fullscreen mode

Prop type

Prop is an object with following attributes:

  1. body - body sent from POST, PUT and PATCH methods
  2. params - parameters from named params
  3. query - query object from string like ?p=1&q=2
  4. custom params
  5. matchedPath - path regex
  6. pathPattern - path definition
  7. req - as { method, url }
  8. Any other parameters coming from middlewares


Middlewares are provided through async composition of functions. Each function in chain must return properties which are required for next function in chain, or which are required to pass to controller at the end of the chain.

Let's say we have two async functions and one sync, chained together:

// first we need `useMiddlewares` helper for composition
import { withMiddlewares } from "./tino.js";
const withAuth = async (props) => ({ currentUser: {}, userData: props.body });
const isAdmin = ({ currentUser }) => ({ isAdmin: false, currentUser });
const withDB = async (props) => ({ coll: {}, ...props });

// Then we chain(compose) them:
const composed = useMiddlewares(

// Then we wrap our controller with it:
const use = composed((props) => ({ resp: props.currentUser }));
app.get(() => ({ path: "/myapi", use }));
Enter fullscreen mode Exit fullscreen mode

Throw exceptions in middlewares (protect routes)

If you want to return early from middleware chain, just throw an exception with same definition as controller's { resp, status?, type? }:

const withAuth = async () => { throw { resp: "Boom", status: 401 } };
Enter fullscreen mode Exit fullscreen mode

So whatever you return from your controller, your endpoint result will be:

HTTP/1.1 401 Unauthorized
content-length: 4
content-type: text/plain

Enter fullscreen mode Exit fullscreen mode


Responders are different set of functions which are useful for writing your own namespaced endpoints or let other people use your package with Tino.

To define one just add root: true param to endpoint definition:

app.any(() => ({ path: "/api/v2", use: myAPI.v2, root: true }));
Enter fullscreen mode Exit fullscreen mode

.any stands for any of the HTTP methods so your namespace reacts to all of them.

Your myAPI.v2 function will receive ctx object which contains some Deno stuff like:

  req: ServerRequest,
  use, // reference to your function
Enter fullscreen mode Exit fullscreen mode

jsondb responder

This responder comes built-in with Tino. It opens /api path by default and is responsible for restful CRUD operations against local db.json file. To learn more about it check it out:

Thank you for reading this far about tino and I hope you liked it.

Again, to see how tino can be used check out maintained examples. Tino is under heavy development and expect more to come and more articles. ;)

Cheers! 🍻

Top comments (0)