Code Overview
This document provides a brief overview of the code used to create a web application with a database using Accella.
Table Definition
We use Prisma’s schema for defining database tables and migrations. Based on the defined table information, migration files can be generated.
model User { id Int @id @default(autoincrement()) firstName String lastName String age Int?}
O/R Mapping
Accella uses Accel Record, an ORM library designed with the Active Record pattern. It features a synchronous interface, so you don’t need to use await
, and implicit lazy loading of associations.
import { User } from "src/models";
// Create a new userconst user: User = User.create({ firstName: "John", lastName: "Doe",});
// Update the useruser.update({ age: 26,});
// Get all usersfor (const user of User.all()) { console.log(user.firstName);}
// Find a userconst john: User | undefined = User.findBy({ firstName: "John", lastName: "Doe",});
// Delete the userjohn?.delete();
Extending Model Classes
By extending the class corresponding to a model, you can define validations, callbacks, custom methods, and more.
import { before } from "accel-record";import { validates } from "accel-record/validations";import { ApplicationRecord } from "./applicationRecord.js";
export class UserModel extends ApplicationRecord { // Define validations for the model static validations = validates(this, [ ["firstName", { presence: true }], ["lastName", { presence: true }], ]);
@before("save") myCallback() { // this method is called before save }
// Define a method to get the full name get fullName(): string { return `${this.firstName} ${this.lastName}`; }}
Preparing Pages
With file-based routing, no routing configuration is generally needed. Astro components allow you to write JavaScript logic and DOM structure in a single file, similar to React components or Vue SFCs, enabling type-safe template rendering.
---import { paginate } from "accel-web";import { User } from "src/models";import Layout from "../layouts/Layout.astro";
const page = Number(Astro.locals.params.p) || 1;const pager = paginate(User.order('id', 'desc'), { page, per: 10, window: 2,});const { Nav, PageEntriesInfo } = pager;---
<Layout> <h2>User List</h2> <table> <thead> <tr> <th>ID</th> <th>Email</th> </tr> </thead> <tbody> { pager.records.map((user) => ( <tr> <td>{user.id}</td> <td>{user.email}</td> </tr> )) } </tbody> </table> <!-- Pagination --> <div><Nav /></div> <div><PageEntriesInfo /></div></Layout>
---import { formFor } from "accel-web";import Layout from "src/layouts/Layout.astro";import { User } from "src/models";
// Retrieve the user by id// If the user does not exist, a RecordNotFound error is thrown and a 404 page is displayedconst user = User.find(Astro.params.id);
if (Astro.request.method === "POST") { const { params } = Astro.locals; // Update the email column if (user.update(params.require("user").permit("email"))) { // Redirect to the user list page if the update is successful return Astro.redirect("/users"); } // If the update fails, continue rendering the page and display // the validation error messages from user.errors}const f = formFor(user);const { Form, Label, TextField, Submit } = f;---
<Layout> <h2>Edit User</h2> <Form method="post"> { user.errors.fullMessages.length > 0 && ( <div role="alert"> {user.errors.fullMessages.map((message) => ( <div>{message}</div> ))} </div> ) } <div> <Label for="email" /> <!-- The value of the TextField will reflect the content of user.email --> <TextField attr="email" /> </div> <div> <Submit>Update</Submit> <a href="/users">Cancel</a> </div> </Form></Layout>
Testing
Vitest is set up to allow you to start testing immediately after starting the project. Additionally, factories for Accel Record are provided, making it easy to create test data.
import { User } from "src/models";import { $User } from "./factories/user";
test("create a user", () => { const user = $User.create({ firstName: "John", lastName: "Doe", });
expect(user.fullName).toBe("John Doe"); expect(User.count()).toBe(1);});