| name | javascript-typescript |
| description | Modern JavaScript and TypeScript development best practices (2024-2025). Use for frontend, backend, full-stack, React/Next.js, Node.js, testing, linting, and production deployment. |
| metadata | {"tags":["javascript","typescript","frontend","backend","nodejs","react","nextjs","testing","linting","bun","pnpm"]} |
JavaScript & TypeScript
Modern JS/TS development covering project setup, TypeScript strict mode, React/Next.js patterns, state management, testing, linting, and production deployment. Targets Node.js 20+, TypeScript 5.5+, React 19+, pnpm, bun.
"Make invalid states unrepresentable." — The TypeScript Way
1. Project Setup
Package Manager
| Tool | Best For | Install | Speed | Lockfile |
|---|
| pnpm (recommended) | Monorepos, disk efficiency, strict dependency resolution | corepack enable && corepack prepare pnpm@latest --activate | Fast | pnpm-lock.yaml |
| bun | Runtime + bundler + test runner in one, fastest install | curl -fsSL https://bun.sh/install | bash | Fastest | bun.lockb |
| npm | Default, no setup needed | Built-in with Node.js | Slowest | package-lock.json |
pnpm workflow:
corepack enable
corepack prepare pnpm@latest --activate
pnpm init
pnpm add typescript react react-dom
pnpm add -D @types/react @types/react-dom eslint prettier vitest
pnpm-workspace.yaml:
packages:
- 'packages/*'
- 'apps/*'
pnpm -r install
pnpm --filter web build
pnpm -r exec tsc --noEmit
bun workflow:
curl -fsSL https://bun.sh/install | bash
bun init
bun add typescript react react-dom
bun add -D @types/react @types/react-dom
bun dev
bun test
bun build
bun run server.ts
bun --hot server.ts
When to choose what:
Monorepo with 5+ packages → pnpm (workspace filtering, strict hoisting)
Single app, speed critical → bun (runtime + bundler + test in one)
Enterprise / CI consistency → pnpm (mature, predictable, corepack)
Quick prototype / side project → bun (fastest everything)
Lockfile hygiene (critical for CI reproducibility):
pnpm install --frozen-lockfile
pnpm install --no-frozen-lockfile
bun install --frozen-lockfile
bun install --no-save
TypeScript Configuration (tsconfig.json)
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Key strict flags:
| Flag | Effect |
|---|
strictNullChecks | null/undefined are separate types |
noUncheckedIndexedAccess | arr[0] is T | undefined |
exactOptionalPropertyTypes | ? means absent, not | undefined |
2. Type Best Practices
Prefer Interfaces for Object Shapes
interface User {
id: string;
name: string;
email: string;
}
type Status = 'idle' | 'loading' | 'success' | 'error';
type ApiResponse<T> = { data: T; status: number } | { error: string; status: number };
Discriminated Unions
type LoadingState = { status: 'loading' };
type SuccessState<T> = { status: 'success'; data: T };
type ErrorState = { status: 'error'; error: string };
type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;
function handleState<T>(state: AsyncState<T>): T | null {
switch (state.status) {
case 'loading': return null;
case 'success': return state.data;
case 'error': throw new Error(state.error);
default: return exhaustiveCheck(state);
}
}
function exhaustiveCheck(x: never): never {
throw new Error(`Unhandled case: ${x}`);
}
Branded Types for Type-Safe IDs
type UserId = string & { __brand: 'UserId' };
type OrderId = string & { __brand: 'OrderId' };
function createUserId(id: string): UserId {
return id as UserId;
}
Utility Types
type UpdateUserInput = Partial<Pick<User, 'name' | 'email'>>;
type ApiReturn = ReturnType<typeof fetchUser>;
type ApiParams = Parameters<typeof fetchUser>;
type ConfigMap = Record<string, string>;
type ImmutableUser = Readonly<User>;
type UserData = Awaited<ReturnType<typeof fetchUser>>;
3. Modern JavaScript (ES2022+)
Nullish Coalescing & Optional Chaining
const value = config.timeout ?? 5000;
const name = user?.profile?.name ?? 'Anonymous';
Top-Level Await
const data = await fetch('/api/config').then(r => r.json());
export const config = data;
Private Class Fields
class Counter {
#count = 0;
increment() {
this.#count++;
return this.#count;
}
get #formatted() {
return `Count: ${this.#count}`;
}
}
Array Methods
const nums = [1, 2, 3, 4, 5];
const doubled = nums.map(n => n * 2);
const evens = nums.filter(n => n % 2 === 0);
const sum = nums.reduce((a, b) => a + b, 0);
const firstEven = nums.find(n => n % 2 === 0);
const hasEven = nums.some(n => n % 2 === 0);
const allPositive = nums.every(n => n > 0);
const sorted = nums.toSorted((a, b) => b - a);
const reversed = nums.toReversed();
4. React 19+ Patterns
Server Components (Next.js App Router)
import { db } from '@/lib/db';
export default async function Page() {
const users = await db.query('SELECT * FROM users');
return (
<main>
<h1>Users</h1>
<UserList users={users} />
<UserForm /> {/* Client Component */}
</main>
);
}
'use client';
import { useState } from 'react';
export function UserForm() {
const [name, setName] = useState('');
}
Hooks Best Practices
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
function useApi<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<Error | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(r => r.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
return () => controller.abort();
}, [url]);
return { data, error, loading };
}
function UserProfile({ userId }: { userId: string }) {
const { data: user, error, loading } = useApi<User>(`/api/users/${userId}`);
if (loading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
if (!user) return <NotFound />;
return <UserCard user={user} />;
}
useMemo / useCallback
const sortedUsers = useMemo(
() => users.sort((a, b) => a.name.localeCompare(b.name)),
[users]
);
const handleSubmit = useCallback(
(data: FormData) => {
api.submit(data);
},
[]
);
React 19 Actions
'use server';
export async function createUser(formData: FormData) {
'use server';
const name = formData.get('name') as string;
await db.insert('users', { name });
revalidatePath('/users');
}
import { useActionState } from 'react';
function UserForm() {
const [state, formAction, pending] = useActionState(createUser, null);
return (
<form action={formAction}>
<input name="name" required />
<button disabled={pending}>
{pending ? 'Creating...' : 'Create'}
</button>
{state?.error && <p>{state.error}</p>}
</form>
);
}
5. State Management
Zustand (Recommended)
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
interface UserStore {
user: User | null;
setUser: (user: User | null) => void;
logout: () => void;
}
export const useUserStore = create<UserStore>()(
devtools(
persist(
(set) => ({
user: null,
setUser: (user) => set({ user }),
logout: () => set({ user: null }),
}),
{ name: 'user-storage' }
)
)
);
function Profile() {
const { user, logout } = useUserStore();
}
TanStack Query (Server State)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
staleTime: 5 * 60 * 1000,
});
}
function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (user: NewUser) =>
fetch('/api/users', { method: 'POST', body: JSON.stringify(user) }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
}
6. Testing
Vitest (Recommended over Jest)
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
},
});
import { describe, it, expect } from 'vitest';
import { calculateTotal } from './calculate';
describe('calculateTotal', () => {
it('sums items correctly', () => {
const items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 1 },
];
expect(calculateTotal(items)).toBe(25);
});
it('handles empty cart', () => {
expect(calculateTotal([])).toBe(0);
});
it('throws on negative price', () => {
expect(() => calculateTotal([{ price: -1, quantity: 1 }]))
.toThrow('Price must be positive');
});
});
React Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { UserForm } from './UserForm';
it('submits form with user data', async () => {
const onSubmit = vi.fn();
render(<UserForm onSubmit={onSubmit} />);
fireEvent.change(screen.getByLabelText(/name/i), {
target: { value: 'Alice' },
});
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({ name: 'Alice' });
});
});
Playwright (E2E)
import { test, expect } from '@playwright/test';
test('user can log in', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="email"]', 'user@example.com');
await page.fill('[name="password"]', 'password');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});
7. Linting & Formatting
ESLint (Flat Config)
import js from '@eslint/js';
import ts from 'typescript-eslint';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import prettier from 'eslint-config-prettier';
export default [
js.configs.recommended,
...ts.configs.recommendedTypeChecked,
react.configs.flat.recommended,
reactHooks.configs['recommended-latest'],
prettier,
{
languageOptions: {
parserOptions: {
project: './tsconfig.json',
},
},
rules: {
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-function-return-type': 'off',
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
},
},
];
Prettier
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100
}
8. Node.js Backend
Fastify (Recommended over Express)
import fastify from 'fastify';
import { z } from 'zod';
const app = fastify({ logger: true });
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).optional(),
});
app.post('/users', async (request, reply) => {
const body = createUserSchema.parse(request.body);
const user = await db.users.create(body);
reply.status(201).send(user);
});
app.setErrorHandler((error, request, reply) => {
app.log.error(error);
if (error instanceof z.ZodError) {
reply.status(400).send({ error: 'Validation failed', details: error.errors });
return;
}
reply.status(500).send({ error: 'Internal server error' });
});
await app.listen({ port: 3000 });
tRPC (Type-Safe APIs)
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
export const appRouter = t.router({
user: t.router({
get: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.users.findById(input.id);
}),
create: t.procedure
.input(z.object({ name: z.string(), email: z.string().email() }))
.mutation(async ({ input }) => {
return db.users.create(input);
}),
}),
});
export type AppRouter = typeof appRouter;
import { createTRPCReact } from '@trpc/react-query';
const trpc = createTRPCReact<AppRouter>();
const { data } = trpc.user.get.useQuery({ id: '123' });
9. Integration with Other Skills
| This skill provides | Related skill | For deeper dive |
|---|
| React/Next.js patterns | python-development | FastAPI backend for full-stack |
| Docker for JS apps | docker-containerization | Multi-stage builds, Node.js images |
| K8s deployment | kubernetes-orchestration | Manifests for JS services |
| Security | security-analysis | OWASP for web apps, auth patterns |
| Testing | evolutionary-architecture | Fitness functions, architecture tests |
10. Bun Runtime Deep Dive
Bun is an all-in-one JavaScript runtime (faster than Node.js), bundler, test runner, and package manager.
Bun vs Node.js
| Feature | Bun | Node.js |
|---|
| Runtime speed | ~3x faster | Baseline |
| Package manager | Built-in (bun install) | npm (external) |
| Bundler | Built-in (bun build) | webpack/esbuild/rollup (external) |
| Test runner | Built-in (bun test) | jest/vitest (external) |
| TypeScript | Native (no ts-node) | Requires transpilation |
| ESM/CJS | Both, seamless | ESM still experimental in some cases |
.env loading | Built-in (Bun.env) | Requires dotenv package |
| File I/O | Bun.file() — fast native | fs module |
| SQLite | Built-in (bun:sqlite) | Requires better-sqlite3 |
| Web APIs | Native fetch, WebSocket | Added in v18+ |
Bun Server (Drop-in for Express/Fastify)
const server = Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === '/api/users') {
return Response.json([{ id: 1, name: 'Alice' }]);
}
if (url.pathname === '/api/upload' && req.method === 'POST') {
const formData = await req.formData();
const file = formData.get('file') as File;
await Bun.write(`./uploads/${file.name}`, file);
return Response.json({ uploaded: file.name });
}
return new Response('Not Found', { status: 404 });
},
});
console.log(`Server running at http://localhost:${server.port}`);
Bun SQLite (Built-in)
import { Database } from 'bun:sqlite';
const db = new Database('app.db');
db.run(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
const insert = db.prepare('INSERT INTO users (email) VALUES (?)');
insert.run('alice@example.com');
const query = db.query<{ id: number; email: string }, []>(
'SELECT id, email FROM users WHERE created_at > datetime("now", "-7 days")'
);
const recentUsers = query.all();
Bun File I/O
const file = Bun.file('./data.json');
const text = await file.text();
const json = await file.json();
const arrayBuffer = await file.arrayBuffer();
await Bun.write('./output.json', JSON.stringify(data));
const writer = Bun.file('./large.zip').writer();
for await (const chunk of readableStream) {
writer.write(chunk);
}
writer.end();
Bun Testing
import { describe, test, expect } from 'bun:test';
describe('math', () => {
test('adds numbers', () => {
expect(1 + 1).toBe(2);
});
test('async operations', async () => {
const result = await fetch('http://localhost:3000/api/health');
expect(result.status).toBe(200);
});
test('snapshot testing', () => {
expect({ name: 'bun', version: '1.0' }).toMatchSnapshot();
});
});
Bun Bundling
bun build ./src/index.ts --outdir ./dist --target bun
bun build ./src/index.ts --outdir ./dist --target browser
bun build ./src/index.ts --outdir ./dist --target node
bun build ./src/index.ts --outdir ./dist --minify --sourcemap
Bun in Docker
# Dockerfile — multi-stage with Bun
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build ./src/server.ts --outdir ./dist --target bun
FROM oven/bun:1-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["bun", "./dist/server.js"]
Bun Environment
const apiKey = Bun.env.API_KEY;
const port = Number(Bun.env.PORT) || 3000;
const nodeEnv = process.env.NODE_ENV || 'development';
11. pnpm Monorepo Deep Dive
pnpm workspaces are the most disk-efficient and strict monorepo solution.
Workspace Structure
my-monorepo/
├── pnpm-workspace.yaml
├── package.json # root scripts + shared devDeps
├── pnpm-lock.yaml
├── turbo.json # (optional) build pipeline
├── packages/
│ ├── ui/ # shared UI component library
│ │ ├── package.json # { "name": "@myrepo/ui" }
│ │ └── src/
│ ├── utils/ # shared utilities
│ │ ├── package.json # { "name": "@myrepo/utils" }
│ │ └── src/
│ └── types/ # shared TypeScript types
│ ├── package.json # { "name": "@myrepo/types" }
│ └── src/
└── apps/
├── web/ # Next.js app
│ ├── package.json # dependencies: { "@myrepo/ui": "workspace:*" }
│ └── src/
└── api/ # Fastify/Hono API
├── package.json
└── src/
pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'
- '!**/test/**'
Root package.json
{
"name": "my-monorepo",
"private": true,
"packageManager": "pnpm@9.0.0",
"scripts": {
"build": "pnpm -r build",
"dev": "pnpm --parallel dev",
"test": "pnpm -r test",
"lint": "pnpm -r lint",
"typecheck": "pnpm -r typecheck",
"clean": "pnpm -r exec rm -rf dist node_modules/.cache"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.5.0",
"eslint": "^9.0.0",
"prettier": "^3.0.0"
}
}
Workspace Dependencies
{
"name": "@myrepo/web",
"dependencies": {
"next": "^14.0.0",
"@myrepo/ui": "workspace:*",
"@myrepo/utils": "workspace:^"
},
"devDependencies": {
"@myrepo/types": "workspace:*"
}
}
pnpm Workspace Commands
pnpm install
pnpm --filter @myrepo/web add next
pnpm --filter @myrepo/ui add -D @types/react
pnpm --filter @myrepo/web add @myrepo/ui --workspace
pnpm -r build
pnpm --parallel dev
pnpm --filter @myrepo/web dev
pnpm -r exec tsc --noEmit
pnpm -r exec rm -rf dist node_modules
rm -rf node_modules pnpm-lock.yaml
pnpm install
pnpm Catalogs (pnpm 9.5+) — Unified Versions
packages:
- 'packages/*'
- 'apps/*'
catalog:
react: ^18.3.1
react-dom: ^18.3.1
typescript: ^5.5.0
eslint: ^9.0.0
catalogs:
types:
'@types/react': ^18.3.3
'@types/node': ^20.14.0
{
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:"
},
"devDependencies": {
"@types/react": "catalog:types"
}
}
pnpm Overrides — Emergency Patches
{
"pnpm": {
"overrides": {
"lodash": "4.17.21",
"vite@<5.0.0": "5.0.0"
}
}
}
pnpm + Turbo (Build Pipeline)
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["build"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
npx turbo build
npx turbo test
npx turbo dev --filter=@myrepo/web
References