Utilities
Learning objectives
- You know of the utility of utilities (pun intended).
Similar to dependencies and configurations, utility functions can also be added to your project. These are not dependencies, but rather functions that you can use in your project. For example, you can create a function that returns the current date and time, or a function that returns the current user's name. Utilities are often placed to a separate folder called utils
.
tree --dirsfirst
.
├── config
| └── etaConfig.js
├── controllers
│ ├── authController.js
│ ├── mainController.js
│ └── todoController.js
├── services
│ ├── sessionService.js
│ ├── todoService.js
│ └── userService.js
├── middlewares
│ └── middlewares.js
├── templates
│ ├── login.eta
│ ├── main.eta
│ ├── registration.eta
│ ├── todo.eta
│ └── todos.eta
├── utils
| └── ...
├── app.js
├── app-run.js
└── deps.js
As an example of a utility function that could be useful, let's create a function that can be used to render a given view template with data and the user from session, if the user exists. Let's call the function htmlWithUser
, and place the function into a file called renderUtils.js
, which is placed to the folder utils
.
To be able to render a view template with data and the user from the session, the function needs the context, the name of the view template, and the data. Thus, the function receives the three parameters.
import { eta } from "../config/etaConfig.js";
const htmlWithUser = async (c, viewTemplate, data) => {
// ...
}
export { htmlWithUser };
In the following, we assume that the application uses the
addUserToContextMiddleware
, which adds the user from the session to the context. If this would not be in use, we'd need to use thegetUserFromSession
function from thesessionService.js
.
The implementation would be such that if the function is not given any data as a parameter, we'll start with an empty object as the data. After this, we set the property user
of the data using the user from the context.
import * as sessionService from "../services/sessionService.js";
import { eta } from "../config/etaConfig.js";
const htmlWithUser = async (c, viewTemplate, data) => {
data = data || {};
data.user = c.user;
// ...
};
export { htmlWithUser };
Finally, we render the given view template with the data, and return it as a response. Such a utility function would look as follows.
import * as sessionService from "../sessionService.js";
import { eta } from "../config/etaConfig.js";
const htmlWithUser = async (c, viewTemplate, data) => {
data = data || {};
data.user = c.user;
return c.html(eta.render(viewTemplate, data));
};
export { htmlWithUser };
Now, with such a utility function, we could adjust our mainController.js
to match the following.
import { htmlWithUser } from "../utils/renderUtils.js";
const showMain = async (c) => {
return await htmlWithUser(c, "main.eta");
};
export { showMain };
When are utility functions needed?
A question that might pop into mind is "how do I know when to create a utility function", while another might be "when are utility functions needed?". A good possible answer here is to rely on the rule of three -- "Three strikes and you refactor". The rule of three notes that two copies of the same (or similar) code do not need to be worried about, but three instances of the same (or similar) code should be refactored and extracted to a new function.