Skip to main content

Dinja

Dinja is a scalable, modular, and easy to use web framework built specificially for Deno. Dinja is cross platform, easy to install, and very easy to quickly get up to speed with.

The Dinja framework relies on a series of technologies typically used in web development:

  • Pogo: Pogo is the web API and server underlying Dinja
  • Denjucks: Denjucks is the templating engine used to render templates
  • Sqlite: Sqlite is the database engine used for Dinja

Installation

To install Dinja, simply copy and paste the contents of the dinja.js file in this repository. For systems with curl installed, Dinja can be downloaded on the command line via the curl command:

curl "https://raw.githubusercontent.com/denjucks/dinja/master/dinja.js" > dinja.js

Usage

The dinja.js file acts as a command line interface for Dinja. To create a new Dinja project, run this command:

deno -A dinja.js createproject

This will create a Dinja project in the current directory with the following structure:

|--applets
|   |--mainapp
|      |--templates
|      |  |--_base.html
|      |  |--index.html
|      |
|      |--static
|      |  |--dinja.png
|      |
|      |--router.js
|      |--models.js
|
|--utilities
|   |--util.js
|
|--server.js
|--dinja.js

The web server can be run via the following command:

deno -A server.js 44444

This will start the web server on port 44444. You can connect to the web server using the browser at the following URL:

http://localhost:44444/mainapp

Understanding the Dinja file structure

Each dinja project starts with the following structure:

|--applets
|   |--mainapp
|      |--templates
|      |  |--_base.html
|      |  |--index.html
|      |
|      |--static
|      |  |--dinja.png
|      |
|      |--router.js
|      |--models.js
|
|--utilities
|   |--util.js
|
|--server.js
|--dinja.js

At the base directory, the following 2 files and 2 folders are found:

  • /applets: This folder contains all the applets available for this Dinja project. Dinja uses and applet, making it very easy to register/deregister, and understand the different pieces of the Dinja project.
  • /utilities: This folder contains utilities scripts used within the Dinja project elsewhere (such as in the applets). Generally you won’t need to modify this folder in any way.
  • /server.js: This file is the entry point of the server, and contains configurations for the web server. You’ll register new applets you create within this file.
  • /dinja.js: This file is the command line interface you’ll use to interact with the Dinja project

Let’s look at server.js more. The contents of this file are as follows:

import pogo from "https://raw.githubusercontent.com/denjucks/dinja/master/lib/pogo/main.js";
import * as sqlite from "https://raw.githubusercontent.com/denjucks/dinja/master/lib/sqlite.js";

// Creating the server and setting the port
let port = 55555;
if (Deno.args[0]) {
    port = Number.parseInt(Deno.args[0]);
}

const server = pogo.server({
    port: port
});

// Creating a database for the webapp
export const database = await sqlite.open("sqlite.db");

// Importing routers and adding routers to the server
import * as mainapp from "./applets/mainapp/router.js";
server.router.add(mainapp.router);

// Starting the server
server.start();

You can see the server will set a default port if none is specified, will connect to an Sqlite database, will register all the applets and add their routers to the server router, and will start the server.

Applets

Notice that when a Dinja project is created, it creates an applet for you, called mainapp, and will add it to the server router. Let’s look at the router.js and models.js files:

router.js:

import * as path from "https://raw.githubusercontent.com/denjucks/dinja/master/lib/path.js";
import pogo from "https://raw.githubusercontent.com/denjucks/dinja/master/lib/pogo/main.js";
import denjucks from "https://raw.githubusercontent.com/denjucks/dinja/master/lib/denjucks.js";
import * as utilities from "../../utilities/util.js"
import {database} from "../../server.js"

// Creating and exporting the router
export const router = pogo.router();

// Creating a denjucks environment
const __dirname = utilities.crossPlatformPathConversion(new URL(".", import.meta.url).pathname);
const denjucksEnv = new denjucks.Environment(new denjucks.FileSystemLoader(path.join(__dirname, "templates")));

// Setting the path prefix for this route
const pathPrefix = "/mainapp";

// Creating the static file route for this router
router.get(pathPrefix + "/static/{filename}", async (request, handler) => {
    const fileName = request.params.filename;
    const buffer = await Deno.readFile(path.join(__dirname, "static", fileName));
    const mimetype = utilities.determineMimeType(fileName)
    
    return handler.response(buffer).type(mimetype);
});

// Creating a route for this router
router.get(pathPrefix + "", (request) => {
    return denjucksEnv.render("index.html");
});

In this file, first a Pogo router is created, then a Denjucks environment is set to specify where templates will be stored. Generally you won’t need to change these lines of code.

Next, the pathPrefix variable is set. This variable creates a prefix that all routes within this applet will need to take to reach the applet. In the case of the default applet, the pathPrefix is set to “mainapp”, meaning that all routes within this router will be found at localhost:55555/mainapp. This variable can be changed to another value to change the prefix for the router.

After that, a route is created for this applet for all static files in the applet. The static files for an applet can be found within their static folder. This route can be removed if this applet won’t be serving any static content (for example if this applet is a REST API applet).

Finally, a base route is created for this applet. In this case, the route will render the index.html file within the applet’s template folder. The index.html file contains a Denjucks template:

{% extends "_base.html" %}

{% block content %}
    <style>
        html, body {
            margin: 0px;
        }
    </style>
    
    <div style="display: flex; width: 100vw; height: 100vh;">
        <div style="margin: auto;">
            <h1 style="text-align: center; font-family: sans-serif">Welcome to:</h1>
            <img src="/mainapp/static/dinja.png">
            <p style="font-family: sans-serif">
                Dinja is based on several libraries:
                <ul>
                    <li><a href="https://deno.land/x/pogo/">Pogo</a>: the web framework used for dinja</li>
                    <li><a href="https://github.com/denjucks/denjucks">Denjucks</a>: the templating engine used for dinja</li>
                    <li><a href="https://deno.land/x/sqlite/">Sqlite</a>: the database used for dinja</li>
                </ul>
            </p>
            
            <p>Documentation for Dinja can be found <a href="https://github.com/denjucks/dinja">here</a>.</p>
        </div>
    </div>
{% endblock content %}

Notice how index.html extends off the parent template _base.html, which contains the following:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>My Webpage</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        {% block content %}
        
        {% endblock content %}
    </body>
</html>

Denjucks templates make it easy to scale the applet for multiple routes, as they can extend from other templates. More information and documentation for Denjucks can be found here

Adding more routes to an applet

New routes can be added in the router.js. Let’s say we want to add a new route:

router.get(pathPrefix + "/newroute", (request) => {
    return denjucksEnv.render("newtemplate.html");
});

In this case, we can access the route at this URL:

http://localhost:55555/mainapp/newroute

And the result will be the rendered file newtemplate.html.

Internally Dinja uses Pogo for it’s web server, and more information and documentation for Pogo can be found here

Creating more applets

To create a new applet, simply run the following Dinja command:

deno -A dinja.js createapp mynewapp

This will create a new applet within the applets folder called mynewapp. It will have the same contents as mainapp when it was first created, but won’t by default be registered. To register it, add the following code to server.js:

// Importing routers and adding routers to the server
import * as mainapp from "./applets/mainapp/router.js";
server.router.add(mainapp.router);

import * as mynewapp from "./applets/mynewapp/router.js";
server.router.add(mynewapp.router);

And now you’ll have access to all the routes created for the applet.

Using the Sqlite database

By default Dinja uses the Sqlite database, a useful database for prototyping and for smaller scale web applications. Given Deno is newer and still in development, there aren’t too many options for cross database ORMs. However, as these develop, new database engines will be added to Dinja making it easy to use other databases as well.

Each applet contains a models.js file which contains all the schemas for the Sqlite database for this applet. The contents of the models.js include the following:

export const models = [
    `CREATE TABLE IF NOT EXISTS mainappUsers (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        userName TEXT,
        hashedPassword TEXT,
        firstName TEXT,
        lastName TEXT,
        email TEXT,
        phoneNumber TEXT,
        creationTime DATETIME DEFAULT CURRENT_TIMESTAMP
    )`,
]

All models for the applet are created as strings within the models array. New models can be added in a similar way as the above model using CREATE TABLE SQL statements.

To migrate all models within all applets, we can use this Dinja command:

deno -A dinja.js migrate

Notice that the router.js files contain an exported database variable from the server.js file. The database can be accessed within the routes in the router using this variable.

For more information on how to use and query the database, see the Sqlite module documentation here

Future of Dinja

Plans for future updates to Dinja include:

  • Using an ORM for the database engine allowing multiple databases to be used and allowing databases to be easily interchanged
  • Update Dinja with any new changes to the Deno API after Deno 1.0 releases
  • Optimize and further document the code internally
  • Add more commands to the dinja.js CLI as more features are added to Dinja
  • Adding Typescript types to everything