Architecture: Layered Architecture
Learning objectives
- You know the concept layered architecture.
- You know how to divide an application into layers.
The term layered architecture refers to organizing code into layers that each have their own responsibility within the application. The term layer refers to a group of related functionality that is separated from other functionality in the application. The term architecture refers to the overall structure of the application.
Layers in an application
Layers in layered architecture are organized horizontally, where each layer abstracts away implementation details of the layer below. Layers only communicate within the layer and with the layers below them (i.e. call the functions exposed by the layers below them). Communication with the above layers is not allowed, i.e. a lower layer must not call a function of an above layer.
As an example, the image below shows a layered architecture that consists of three layers: a presentation layer, a business logic layer, and a database access layer.

When considering the above structure, one way to think of this would be to see the view templates as the representation layer, the functionality that routes requests as business logic, and the functionality that interacts with data as the database access layer. Often, in the context of web applications, we use terms view and service instead of presentation and business logic. In addition, a layer for controllers is often added to the architecture, which is responsible for handling incoming requests and directing them to the correct functionality.
Controlling requests
A part of the code in our app.js
has actually been responsible for controlling which functionality is called for which request. In practice, web applications often have an additional layer for controllers, which handle specific incoming requests. Using the terms view and service instead of presentation and business logic, and adding controllers into our architecture, an architecture with four layers would look as follows.

Let's briefly look into creating a separate controller layer into the following count application.
import { Hono } from "https://deno.land/x/hono@v3.12.11/mod.ts";
import * as countService from "./countService.js";
const app = new Hono();
app.get("/", async (c) => c.text(await countService.getCount()));
app.post("/", async (c) => {
await countService.incrementCount();
return c.text(await countService.getCount());
});
Deno.serve(app.fetch);
The application has two routes, one for getting the current count and one for incrementing the count. To separate the controllers, we would create a file countController.js
, which would provide the functionality for handling the routes. The file would look as follows.
import * as countService from "./countService.js";
const getCount = async (c) => {
return c.text(await countService.getCount());
}
const incrementAndGetCount = async (c) => {
await countService.incrementCount();
return c.text(await countService.getCount());
}
export { getCount, incrementAndGetCount };
We would then import the functions from the countController.js
file to the app.js
file, and use them for handling the routes. The app.js
file would look as follows.
import { Hono } from "https://deno.land/x/hono@v3.12.11/mod.ts";
import * as countController from "./countController.js";
const app = new Hono();
app.get("/", countController.getCount);
app.post("/", countController.incrementAndGetCount);
Deno.serve(app.fetch);
Note that the app.js
file is now much more concise and the functionality for handling the routes is now separated to the countController.js
file. The countController.js
file is now responsible for handling the routes, and the countService.js
file is responsible for providing the functionality for interacting with the data.
Structure and application size
The above division of the count application into a controller and a service, both with their own responsibilities, is a common way to divide an application into layers. When working with proof-of-concept -like applications, it is possible to also have all the functionality in a single file. However, when working with larger applications, having a meaningful structure is sensible, as it makes the application easier to maintain and extend.
When introducing new concepts and functionality, we use small examples to illustrate the concept and do not divide functionality into files to provide a quick overview of the functionality.