File Upload and Security Validation on Elysia.js #2

M Sadewa Wicaksana
5 min readSep 19, 2024

--

In this next part, based on first meets. We already learn how the basic implement clean code in using Elysia.js. In this story, i explore more to make sure how to upload file in Elysia.js and how the preparation upload file based on security perspective. In the other hand, i also include some security methods like cors, server-timing, and swagger to make my explanation more knowledge to eat.

Concept Validation

To start, Elysia.js comes with built-in functions that handle validation and sanitization when user inputs don’t match the expected values. You can find more details about Elysia.js validation in this https://elysiajs.com/essential/validation.html.

Based on that documentation, elysia.js had a schema build with also as validation using TypeBox. TypeBox is a very fast, lightweight, and type-safe runtime validation library for TypeScript. Elysia extends and customize the default behavior of TypeBox to match for server-side validation. To make it clear, you can see my illustration in the image below

Illustration File Validation on Elysia.js

Implementation Another Security

Because in the previous book, there are no column to store our name file. Therefore, we need to change our prisma schema.

## schema.prisma
model User {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
email String @unique
password String @db.Text
created_at DateTime @default(now())
updated_at DateTime @default(now())
deleted_at DateTime?
file_avatar String? @db.Text
}

To create schema prisma migration run this command

prisma migrate dev --name copy_file_avatar
## routes/RouteUser.ts
import Elysia from "elysia";
import { UserController } from "../controller/UserController";
import { UserCreateModels } from "../model";

export const RouteUsers = (app: Elysia) =>
app.group("/user", (user) => {
user.post(
"/",
async ({ body }) =>
UserController.addUser({
body: body,
}),
{
body: UserCreateModels,
tags: ["User"],
type: "multipart/form-data",
}
);

user.get("/", () => UserController.getUser(), {
tags: ["User"],
});

return user;
});
## model/UserModel.ts
import { t } from "elysia";

export const UserCreateModels = t.Object({
name: t.String({ maxLength: 250, default: "" }),
email: t.String({ format: "email", default: "sampel@tes.id" }),
password: t.String({ default: "12345" }),
file_avatar: t.File({
type: "image/png",
}),
});

export interface UserCreateProps {
name: string;
email: string;
password: string;
file_avatar: File;
}
## controller/UserController.ts
addUser: async ({ body }: { body: UserCreateProps }) => {
try {
const hashPassword = await Bun.password.hash(body.password, {
algorithm: "bcrypt",
cost: 12,
});

const baseDir = "storage/";
const f = await body.file_avatar.text();

const newFileName = `${baseDir}${crypto.randomUUID()}.png`;
await Bun.write(newFileName, body.file_avatar);

const user = await prisma.user.create({
data: {
...body,
password: hashPassword,
file_avatar: newFileName,
},
});

return {
data: user,
message: strings.response.success,
};
} catch (error) {
console.log("🚀 ~ addUser: ~ error:", error);
return {
data: [],
message: strings.response.failed,
};
}
},

Changes from previous part:

  • Route: Change content-type from application/json to multipart/form-data, add tags which in the part below will references to our swagger information tags
  • Model: To make sure that the data type between user request and our expected is same, use typebox validation and add t.File to validate that our expected from that key is file not text
  • Controller: Typebox only can handle our validation based on extensions only, but when some hackers want to hack our systems using file type extensions spoofing we need to add some logic to handle it. Therefore, we use bun to read the file based on text and check the availability metadata on that file. If the metadata is exist, so bun will write the file and saved it into file storage.

Implementation Another Security

In the root main file on src/index.ts add some security considerations like swagger, cors, and server timing.

  • Swagger can be used as documentations also can be interfaces to our users for interaction with our APIs without any additional IDE
  • Cors is implemented to secure our website and to whitelisting our traffic server informations who can access our API
  • Server timing can help you to analyze how the performance of your Endpoint, there are several information you can obtain from that function such as request duration, total duration from start to finish, parse duration and etc

When implement security with Cors, changed origin with domain you want to be listed. To make it more dynamic you can placed the origin url in file .env for example

## .env
URL="api.opendatadesa.id"
## CORS
app.use(
cors({
preflight: true,
origin: Bun.env.URL,
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
})
);

To make it clear and make you easy to understand what the differences between before and after i’ll give you some screenshot

Before Implement Cors
After Implement Cors

In the other hand, when implement server timing our response headers also will append one key the name is Server-Timing.

Before Server Timing
After Server Timing

Swagger is a powerful tool that significantly simplifies collaboration between teams when working with REST APIs. By providing a clear and interactive API documentation, Swagger allows developers, testers, and stakeholders to easily understand and consume the API. It automatically generates documentation from the API’s codebase, keeping it up to date as the API evolves. One of the sample is

//swagger
app.use(
swagger({
documentation: {
info: {
title: "API Boilerplate Elysia.js",
version: "0.0.1-rc",
},
tags: [
{
name: "User",
description: "Endpoints related to user",
},
{
name: "Default",
description: "Basic default templates",
},
],
},
path: "/",
})
);

— -

Download the source code mini-project here.

--

--

M Sadewa Wicaksana
M Sadewa Wicaksana

Written by M Sadewa Wicaksana

Artificial Intelligence and Fullstack Engineering Enthusiast and Still Learning

No responses yet