Code Examples
Explore common patterns and best practices for building applications with the NoEgo framework. Each example includes complete, copy-ready code snippets.
Follow this complete workflow to add a new feature from database to UI.
Step 1: Create Migration
Define your database schema changes with up and down migrations.
CREATE TABLE posts (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
author_id INT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at DATETIME,
FOREIGN KEY (author_id) REFERENCES users(id)
);
CREATE INDEX idx_posts_author ON posts(author_id);
CREATE INDEX idx_posts_deleted ON posts(deleted_at);DROP INDEX IF EXISTS idx_posts_deleted ON posts;
DROP INDEX IF EXISTS idx_posts_author ON posts;
DROP TABLE IF EXISTS posts;Step 2: Create Repository
Use SQLStack with @QueryBinder and co-located SQL files for type-safe database access.
import { Component } from "@noego/ioc";
import type { WriteResult } from "sqlstack";
import { QueryBinder, Query, Single, SqlStackError } from "sqlstack";
export interface Post {
id: number;
title: string;
content: string;
author_id: number;
author_name?: string;
created_at: string;
updated_at: string;
deleted_at: string | null;
}
@QueryBinder()
@Component()
export default class PostsRepo {
// SQL file: getAllPosts.sql
@Query()
getAllPosts(): Promise<Post[]> {
throw new SqlStackError("Not implemented");
}
// SQL file: getPostById.sql
@Single()
@Query()
getPostById(id: number): Promise<Post | null> {
throw new SqlStackError("Not implemented");
}
// SQL file: createPost.sql
@Query()
createPost(data: {
title: string;
content: string;
author_id: number;
}): Promise<WriteResult> {
throw new SqlStackError("Not implemented");
}
// SQL file: updatePost.sql
@Query()
updatePost(data: {
id: number;
title?: string;
content?: string;
}): Promise<WriteResult> {
throw new SqlStackError("Not implemented");
}
// SQL file: deletePost.sql (soft delete)
@Query()
deletePost(id: number): Promise<WriteResult> {
throw new SqlStackError("Not implemented");
}
}-- getAllPosts.sql
SELECT
p.id, p.title, p.content, p.author_id,
u.name AS author_name,
p.created_at, p.updated_at
FROM posts p
JOIN users u ON p.author_id = u.id
WHERE p.deleted_at IS NULL
ORDER BY p.created_at DESC;-- createPost.sql
-- Uses :insert helper for dynamic column insertion
INSERT INTO posts :insert(
title,
content,
author_id,
created_at = datetime('now'),
updated_at = datetime('now')
);Step 3: Create Service
Implement business logic with dependency injection using @Component and @Inject.
import { Component, Inject } from "@noego/ioc";
import PostsRepo from "../repo/posts_repo";
@Component()
export default class PostsService {
constructor(
@Inject(PostsRepo) private repo: PostsRepo
) {}
async getAllPosts() {
return this.repo.getAllPosts();
}
async getPostById(id: number) {
const post = await this.repo.getPostById(id);
if (!post) {
throw new Error("Post not found");
}
return post;
}
async createPost(data: { title: string; content: string; authorId: number }) {
// Validate input
if (!data.title || data.title.trim().length < 3) {
throw new Error("Title must be at least 3 characters");
}
const result = await this.repo.createPost({
title: data.title.trim(),
content: data.content.trim(),
author_id: data.authorId
});
return Number(result.lastInsertId);
}
async updatePost(id: number, data: { title?: string; content?: string }) {
// Verify post exists
await this.getPostById(id);
const result = await this.repo.updatePost({ id, ...data });
if (result.rowsAffected === 0) {
throw new Error("Post not found");
}
}
async deletePost(id: number) {
const result = await this.repo.deletePost(id);
if (result.rowsAffected === 0) {
throw new Error("Post not found");
}
}
}Step 4: Create Controller
Handle HTTP requests. Routes are defined in OpenAPI YAML.
import { Component, Inject } from "@noego/ioc";
import PostsService from "../service/posts_service";
@Component()
export default class PostsController {
constructor(
@Inject(PostsService) private service: PostsService
) {}
async list({ req, res }: any) {
const posts = await this.service.getAllPosts();
return { posts };
}
async getById({ req, res }: any) {
const id = Number(req.params.id);
try {
const post = await this.service.getPostById(id);
return post;
} catch (error) {
res?.status?.(404);
return { error: "Post not found" };
}
}
async create({ req, res }: any) {
const { title, content, authorId } = req.body;
try {
const id = await this.service.createPost({ title, content, authorId });
res?.status?.(201);
return { id };
} catch (error) {
res?.status?.(400);
return { error: (error as Error).message };
}
}
async update({ req, res }: any) {
const id = Number(req.params.id);
const { title, content } = req.body;
try {
await this.service.updatePost(id, { title, content });
res?.status?.(204);
return;
} catch (error) {
res?.status?.(404);
return { error: "Post not found" };
}
}
async remove({ req, res }: any) {
const id = Number(req.params.id);
try {
await this.service.deletePost(id);
res?.status?.(204);
return;
} catch (error) {
res?.status?.(404);
return { error: "Post not found" };
}
}
}module:
posts:
basePath: '/api/posts'
paths:
'/':
get:
x-controller: posts.controller
x-action: list
summary: Get all posts
responses:
'200':
description: List of posts
post:
x-controller: posts.controller
x-action: create
summary: Create a new post
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [title, content, authorId]
properties:
title:
type: string
minLength: 3
content:
type: string
authorId:
type: integer
responses:
'201':
description: Post created
'/{id:\\d+}':
get:
x-controller: posts.controller
x-action: getById
responses:
'200':
description: Post found
'404':
description: Post not found
patch:
x-controller: posts.controller
x-action: update
requestBody:
content:
application/json:
schema:
type: object
properties:
title:
type: string
content:
type: string
responses:
'204':
description: Post updated
delete:
x-controller: posts.controller
x-action: remove
responses:
'204':
description: Post deletedStep 5: Create Page Loader
Server-side data loading with the .load.ts pattern.
type RequestData = any; // Avoids server-only imports
export default async function load(request: RequestData) {
// Fetch posts from API
const response = await fetch('/api/posts');
if (!response.ok) {
return { posts: [], load_error: 'Failed to load posts' };
}
const { posts } = await response.json();
return { posts, load_error: null };
}Step 6: Create Svelte Component
Display data in your UI with Svelte 5 runes.
<script lang="ts">
interface Post {
id: number;
title: string;
content: string;
author_name: string;
created_at: string;
}
let { posts, load_error } = $props<{
posts: Post[];
load_error: string | null;
}>();
</script>
<div class="max-w-4xl mx-auto py-8 px-4">
<h1 class="text-3xl font-bold mb-8">Blog Posts</h1>
{#if load_error}
<p class="text-red-500">{load_error}</p>
{:else if posts.length === 0}
<p class="text-gray-500">No posts yet.</p>
{:else}
<div class="space-y-6">
{#each posts as post}
<article class="border rounded-lg p-6 hover:shadow-md transition-shadow">
<h2 class="text-xl font-semibold mb-2">
<a href="/posts/{post.id}" class="hover:text-orange-600">
{post.title}
</a>
</h2>
<p class="text-gray-600 mb-4">{post.content.slice(0, 150)}...</p>
<div class="text-sm text-gray-400">
By {post.author_name} • {new Date(post.created_at).toLocaleDateString()}
</div>
</article>
{/each}
</div>
{/if}
</div>