SKILL.md
---
name: jm-balanced-coding-patterns
description: jm-balanced-coding-patterns is a set of design patterns and best practices curated by JM to enhance software development efficiency and maintainability, while ensuring code quality and scalability.
category: Software Development
---
# jm-patterns
It's a collection of design patterns and best practices curated by JM to enhance software development efficiency and maintainability. The repository includes implementations of common design patterns, coding standards, and architectural guidelines that can be applied across various programming languages and frameworks.
## References & guides
- [Interacting with 3rd party services](./guides/interacting-with-3rd-party-services.md)
- [Components in React](./guides/react-components.md)
- [Next.js - how to](./guides/nextjs-guide.md)
- [Production systems principles](./guides/production-systems-principles.md)
- [Web Design Guidelines](./guides/web-design-guidelines.md)
guides/interacting-with-3rd-party-services.md
use services, adapters, scoped to the domain (e.g. location db service)
do not use standalone functions etc
# Interacting with 3rd part services
This guide outlines best practices for integrating and interacting with third-party services in applications.
## What to do?
- **Use Service/Adapter Pattern:** Create dedicated service or adapter static (most of the time) classes that encapsulate all interactions with third-party services. This promotes separation of concerns and makes it easier to manage changes in the third-party API.
Example of usage with DB client & service:
```ts
// ./src/infrastructure/db/services/index.ts
import 'server-only';
import { db } from '../client';
export class DbService {
protected static client = db;
}
// ./src/infrastructure/db/services/user.ts
export class UserDbService extends DbService {
static async getUserById(userId: string) {
// ...
return this.client.user.findUnique({ where: { id: userId } });
}
// ...
}
```
Wrap adapters (adjusting data to needs of the service) into static classes as well.
- **Scope to Domain:** Ensure that services/adapters are scoped to specific domains or functionalities (e.g., AIService, PageDbService). This helps in organizing code and makes it easier to locate and maintain.
- **Encapsulate Logic:** All logic related to the third-party service should be encapsulated when possible. Good example is Cloudflare Turnstile HOF for Next.js server actions:
```ts
// ./src/utilities/turnstile/with-turnstile.ts
import 'server-only';
import z from 'zod';
import { validateTurnstileToken } from 'next-turnstile';
const _NotEmptyStringSchema = z.string().min(1);
export class TurnstileError extends Error {
constructor(message: string = 'Invalid Turnstile token') {
super(message);
this.name = 'TurnstileError';
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const withTurnstile = <T extends (...args: any[]) => Promise<any>>(handler: T) => {
return async (token: string | null, ...args: Parameters<T>): Promise<ReturnType<T>> => {
const { data: validatedToken, success } = _NotEmptyStringSchema.safeParse(token);
if (!success) {
throw new Error('Invalid Turnstile token');
}
const turnstile = await validateTurnstileToken({
token: _NotEmptyStringSchema.parse(validatedToken),
secretKey: process.env.TURNSTILE_SECRET_KEY,
});
if (!turnstile.success) {
throw new TurnstileError();
}
return await handler(...args);
};
};
```
Usage:
```ts
// define server action
"use server"
// ...
import { withTurnstile } from '@/utilities/turnstile/with-turnstile';
export const submitContactForm = withTurnstile(async (data: ContactFormShape) => {
const result = await EmailService.sendContactFormEmail(ContactFormSchema.parse(data));
if (!result.accepted.length) {
throw new Error('Failed to submit form');
}
});
```
Use on the frontend:
```tsx
// ...
const ContactForm = () => {
// ...
const { turnstile, turnstileToken, setTurnstileToken, verifyTurnstile, unverifyTurnstile } = useFormTurnstile();
const onSubmit = async (data: ContactFormShape) => {
try {
await submitContactForm(turnstileToken, data);
toast.success(t('success'));
setTurnstileToken(null);
turnstile.reset();
} catch (error) {
// handle error...
}
}
return <form onSubmit={onSubmit}>
{/* form fields */}
<Turnstile
sitekey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!}
onSuccess={verifyTurnstile}
onError={unverifyTurnstile}
appearance="execute"
/>
</form>
}
```
Do not limit yourself to only static methods, not to HOFs. Feel free to use different abstractions as needed, but keep them structured and scoped.
## What NOT to do?
Do not define standalone functions for interacting with third-party services, especially some that are "flying around" the codebase. Prefer structured, well scoped (local/global) solutions, like above.
README.md
# jm-balanced-coding-patterns
This document outlines a set of coding patterns and best practices curated by JM to enhance software development efficiency and maintainability, while ensuring code quality and scalability. The goal is to make sure we can move fast, but yet the software stays scaleable & possible to extend in the future, after successful validation. It's supposed to give maximum outcome to startups and small teams.
[See more](./SKILL.md)
AGENTS.md
# jm-balanced-coding-patterns
Version: 0.0.1
> **Note:**
> This document is mainly for agents and LLMs to follow when maintaining,
> generating, or refactoring TypeScript codebases.
---
## Abstract
This document outlines a set of coding patterns and best practices curated by JM to enhance software development efficiency and maintainability, while ensuring code quality and scalability. The goal is to make sure we can move fast, but yet the software stays scaleable & possible to extend in the future, after successful validation. It's supposed to give maximum outcome to startups and small teams.
---
## Table of Contents
- [SKILL.md](./SKILL.md) - Overview of the skill
- [Guides](./guides) - Collection of guides on specific topics
guides/production-systems-principles.md
# Production Systems Principles
This guide outlines the best practices for building applications & systems.
## What to care about?
While building applications we should care about speed and quality, but the most important principle is to make sure the system is safe. By safe we mean:
- we control the input into the system - we validate and sanitize what comes into APIs/services (e.g. with `zod` or similar libraries, in TypeScript codebases)
- we handle errors and failures gracefully - we don't crash or leak sensitive data
- (additional) we log and monitor.
After that we can focus on speed of the development and quality of the UX & code.
guides/nextjs-guide.md
# Next.js (App Router) Conventions
## Repository Structure (Recommended Boundaries)
Suggested layering (rename folders to match your repo):
- `src/app`: routing + application/domain code (Next.js App Router).
- `src/features`: large reusable feature modules (mini-domains).
- `src/infrastructure`: external systems (DB, auth, payments, email, analytics, storage, rate-limit, integrations).
- `src/ui`: reusable UI components that cross domain boundaries.
- `src/utilities`: cross-cutting helpers/HOFs.
- `src/schemas` + `src/types`: Zod schemas + derived TS types.
## Server vs Client
- Default to Server Components. Add `'use client'` only when you need hooks, state, effects, browser APIs, or client-only libraries.
- Mark server-only modules with `import 'server-only';` to prevent accidental client bundling.
- Mark server actions with `'use server';` and keep them small/typed.
## Route Handlers
- Validate request input early (`zod`), return explicit `{ message, code }` JSON on expected failures.
- Prefer `NextResponse.json(...)` for structured errors.
- If you support Edge and a vendor SDK is incompatible, use `fetch` and keep the logic isolated behind a small adapter.
## Route Params / Segment Params
Use `params: Promise<{ slug: string }>` and `const { slug } = await params`.
## Server Actions: Standard Wrapper (`withProtectedAction` pattern)
Use a thin wrapper for the common concerns:
- auth/session lookup (usually from `next/headers`)
- payload validation (Zod)
- output validation (optional but recommended)
- unified return shape: `{ data, failure }` (avoid throwing for expected business failures)
Reference implementation (adapt to your auth + logger):
```ts
import { headers } from 'next/headers';
import 'server-only';
import { type z } from 'zod';
type Success<T extends object> = { data: T; failure: null };
type Failure = { data: null; failure: { message: string; code?: string } };
export const withProtectedAction = <
P extends Record<string, unknown>,
O extends Record<string, unknown>,
TUser,
>(
handler: (payload: P & { user: TUser }) => Promise<Success<O> | Failure>,
opts: { actionId: string; schemas: { payload: z.ZodType<P>; output: z.ZodType<O> } },
) => {
return async (_payload: P): Promise<Success<O> | Failure> => {
// 1) auth/session lookup (replace getSession with your auth provider)
const session = await getSession({ headers: await headers() }); // { user: TUser } | null
if (!session) return { data: null, failure: { message: 'Unauthorized', code: 'unauthorized' } };
// 2) payload validation
const parsed = opts.schemas.payload.safeParse(_payload);
if (!parsed.success)
return { data: null, failure: { message: 'Invalid payload', code: 'invalid_payload' } };
// 3) execute
const result = await handler({ ...parsed.data, user: session.user });
// 4) output validation (if success)
if (result.data) opts.schemas.output.parse(result.data);
return result;
};
};
```
Client usage pattern:
- Call server actions inside `startTransition`.
- Display `result.failure.message` via UI (or toast if no place) instead of `try/catch` for expected failures.
## Caching + Revalidation (Mutation Follow-ups)
- When a server action or route handler mutates data used by routes, explicitly revalidate:
- `revalidatePath('/some/path')` for path-based invalidation
- `revalidateTag('tag')` if you use fetch tags (on the server-side)
- Keep revalidation calls next to the mutation (same function) so cache behavior stays discoverable.
## Auth handling (RSC)
To encapsulate auth logic, and utilize RSC, use `withAuth` HOC for server components auth protection. Here is a reference implementation:
```tsx
import { headers } from 'next/headers';
import { permanentRedirect } from 'next/navigation';
import { type FC } from 'react';
import 'server-only';
import { Path } from '@/constants/path.enum';
import { auth } from '@/infrastructure/auth'; // Better Auth library abstraction
import { logger } from '@/infrastructure/logger';
import { type User } from '@/types/auth';
/** @description Higher-order component to wrap a RSC page/component with authentication.*/
export const withAuth = <T extends object, R extends boolean = false>(
Component: FC<R extends true ? T : T & { user: User }>,
options: { id?: string; reverse?: R } = {},
): FC<T> => {
// eslint-disable-next-line react/display-name
return async (props: T) => {
const session = await auth.api.getSession({
headers: await headers(),
});
logger.info(`withAuth: session retrieved; is session - ${!!session}`);
// handle reverse auth (e.g. login/register pages)
if (options.reverse === true) {
if (session) {
if (options.id) {
logger.error(
{
pageId: options.id,
userId: session.user.id,
},
'withAuth (reversed): User is authenticated. Redirecting to dashboard page.',
);
}
return permanentRedirect(Path.Builder);
}
// @ts-expect-error typescript doesn't infer the correct type - it should not expect user
return <Component {...props} />;
}
if (!session) {
if (options.id) {
logger.error(
{ pageId: options.id },
'withAuth: User is not authenticated. Redirecting to login page.',
);
}
return permanentRedirect(Path.Login);
}
return <Component {...props} user={session.user} />;
};
};
```
Example usage:
```tsx
import { withAuth } from '@/utilities/auth/with-auth';
// ...
const DashboardPage = withAuth(
async ({ user }) => {
const data = await UserDbService.getProfile(user.id)
// ...
},
{ id: 'dashboard-page' },
)
```
guides/react-components.md
# React Conventions (Framework-Agnostic)
Use functional components and arrow functions.
## Definition and Typing
- Use `FC` types e.g. `FC<PropsWithChildren<SidebarProps>>`.
- Define prop types explicitly; prefer `interface` over `type` for props.
- Destructure props in the function signature for clarity.
## Tailwind + Styling
- Use Tailwind utility classes as the default.
- Use a `cn(...)` helper for class composition and Tailwind conflict resolution (classnames + tailwind-merge).
## Component Patterns
Common patterns (use intentionally, not by default):
- Compound components (static properties or object export), e.g. `Modal.Header`, `Sidebar.ContextProvider`.
- Suspense wrappers with skeleton fallbacks (for data fetches and lazy boundaries)
- `useTransition` for server-action calls from client components; show non-blocking pending UI
- `data-testid` attributes for stable test selectors in UI components
- if there is complex internal state management, consider using `useReducer` over `useState`
- use container + render props patterns for complex components, that expose internal state to children & allow custom rendering
- context providers for shared state (theming, modals, sidebars, etc.)
- use portals for modals, tooltips, dropdowns, and other overlay components
guides/web-design-guidelines.md
# Web Design Guidelines
Minimal set of guidelines for web product design, focused on simplicity, speed, and user experience.
- every interaction happens in 100ms
- no product tours
- url /slugs are short and simple, no UIDs
- persistent resumeable state
- not more than 3 colors
- no visible scrollbars
- all navigation is under 3 steps
- copyable svg logo + brandkit
- skeleton loading states
- copy paste from clipboard
- larger hit targets for buttons/inputs
- honest one click cancel
- cmd + k = search
- very minimal tooltips
- copy is active voice, max 7 words per sentence
- optical alignment vs geometric
- optimized for L to R reading
- reassurance about loss