Almost a Walking Skeleton
When starting to build an application, it is common to start with a walking skeleton. A walking skeleton is a version of the application that has some end-to-end functionality. It is used to set the base for the internal structure of the application and to provide a running application that can be built upon in the future.
We start with the following directory structure.
tree --dirsfirst
.
├── config
├── database
│ └── database.js
├── middlewares
│ ├── errorMiddleware.js
│ ├── renderMiddleware.js
│ └── serveStaticMiddleware.js
├── routes
│ ├── apis
│ ├── controllers
│ │ └── mainController.js
│ └── routes.js
├── services
├── static
├── tests
├── views
│ ├── layouts
│ │ └── layout.eta
│ ├── partials
│ └── main.eta
├── app.js
└── deps.js
While most of the folder names might be intuitive, here is a brief summary of the folders and their upcoming contents.
- config contains project configurations such as credentials used for database connection.
- database contains database-related content, including code related to creating a database client.
- middlewares contains the middlewares used in the project.
- routes contains the route mappings for the project. It has two subfolders:
- apis folder contains routes that are used for building APIs.
- controllers folder contains routes that are related to creating views for the users of the application.
- services contains any services used in the project.
- static contains any static files for the project that can be directly served without the need for additional processing.
- tests contains application tests. We will learn a bit about testing applications in the future.
- utils contains generic helper functions and other utilities that are used in the project.
- views contains files used for generating views; in our case, these are Eta files.
The contents of deps.js
are as follows.
export { configure, renderFile } from "https://deno.land/x/eta@v2.2.0/mod.ts";
export {
Application,
Router,
send,
} from "https://deno.land/x/oak@v12.6.1/mod.ts";
import postgres from "https://deno.land/x/postgresjs@v3.4.4/mod.js";
export { postgres };
This means that we can import the libraries that we wish from the file deps.js
in the project instead of referring to the external URLs within the application. For example, the database.js
would be as follows.
import { postgres } from "../deps.js";
let sql;
if (Deno.env.get("DATABASE_URL")) {
sql = postgres(Deno.env.get("DATABASE_URL"));
} else {
sql = postgres({});
}
export { sql };
The renderMiddleware.js
would look as follows (this differs a bit from what we've used previously -- previously, we've included the configure
-call within the render
function to accommodate automated testing performed in the course; here, we do not have to do that):
import { configure, renderFile } from "../deps.js";
configure({
views: `${Deno.cwd()}/views/`,
});
const renderMiddleware = async (context, next) => {
context.render = async (file, data) => {
context.response.headers.set("Content-Type", "text/html; charset=utf-8");
context.response.body = await renderFile(file, data);
};
await next();
};
export { renderMiddleware };
The serveStaticMiddleware.js
would look as follows:
import { send } from "../deps.js";
const serveStaticMiddleware = async (context, next) => {
if (context.request.url.pathname.startsWith("/static")) {
const path = context.request.url.pathname.substring(7);
await send(context, path, {
root: `${Deno.cwd()}/static`,
});
} else {
await next();
}
};
export { serveStaticMiddleware };
And the app.js
would look as follows:
import { Application } from "./deps.js";
import { errorMiddleware } from "./middlewares/errorMiddleware.js";
import { renderMiddleware } from "./middlewares/renderMiddleware.js";
import { serveStaticMiddleware } from "./middlewares/"
import { router } from "./routes/routes.js";
const app = new Application();
app.use(errorMiddleware);
app.use(renderMiddleware);
app.use(router.routes());
app.listen({ port: 7777 });
In our case, the walking skeleton is just showing a simple page with the text "Hello!" (so, technically, it is not even a walking skeleton). To achieve this, we route all GET requests to the root path of the application to a function called showMain
in the mainController.js
. The mainController.js
is as follows:
const showMain = ({ render }) => {
render("main.eta");
};
export { showMain };
And the routes.js
is as follows:
import { Router } from "../deps.js";
import * as mainController from "./controllers/mainController.js";
const router = new Router();
router.get("/", mainController.showMain);
export { router };
We already use a layout, although it is very simple. Effectively, it only includes the body received form the file using the layout. The layout.eta
is as follows:
<%~ it.body %>
And finally, the main.eta
is as follows.
<% layout("./layouts/layout.eta") %>
<h1>Hello!</h1>
Now, when you launch the application and make a GET request to the root path of the application, you should receive a page with the Hello! text as a response.
curl http://localhost:7777
<h1>Hello!</h1>%
When the application works, our starting point is ready.