Junior Developer — Daily Essentials

JAVASCRIPT · REACT + REDUX · SQL · LINUX · NESTJS · TYPESCRIPT · DSA · INFRA ARCH · FS TOOLING · DOCKER · NEXT.JS — QUICK REFERENCE

JS ES6+ React + Redux SQL / PostgreSQL Linux CLI NestJS TypeScript DSA Infra Arch FS Tooling Docker Next.js
1 JavaScript

Variables & Types

Declaration

let   x = 5;          // reassignable
const PI = 3.14;     // constant
var   old = 'avoid';  // function-scoped

Types

// Primitives
typeof 42          // "number"
typeof "hi"       // "string"
typeof true       // "boolean"
typeof undefined  // "undefined"
typeof null       // "object" ⚠️ bug
typeof Symbol()   // "symbol"
typeof 42n        // "bigint"
// Reference
typeof {}  // "object"
typeof [] // "object" (use Array.isArray)
typeof fn // "function"

Truthy / Falsy

// Falsy: false, 0, "", null,
//        undefined, NaN
// Everything else is truthy

Scope & Closures

Hoisting

// var declarations hoisted (not value)
console.log(x); // undefined
var x = 5;
// let/const: TDZ error if used early

// Function declarations hoisted fully
greet(); // works!
function greet() { ... }

Closure

function counter() {
  let count = 0;
  return () => ++count;
}
const inc = counter();
inc(); // 1
inc(); // 2
// Inner fn "closes over" count

Scope Chain

let outer = 'A';
function fn() {
  let inner = 'B';
  // can access outer, not vice versa
}

Functions & this

Function Types

// Declaration
function add(a, b) { return a + b; }

// Expression
const add = function(a, b) { ... };

// Arrow (no own `this`)
const add = (a, b) => a + b;

// Default params
function greet(name = 'World') {
  return `Hello ${name}`;
}

// Rest params
function sum(...nums) {
  return nums.reduce((a, b) => a + b);
}

this Binding

// Regular fn: this = caller
// Arrow fn: this = surrounding scope
const obj = { name: 'Bob',
  greet() { return this.name; }
};
obj.greet(); // "Bob"
// Explicit bind:
fn.call(ctx, a, b)
fn.apply(ctx, [a, b])
const bound = fn.bind(ctx);

ES6+ Essentials

Destructuring

// Object
const { name, age = 0 } = user;
const { name: n } = user; // rename

// Array
const [first, second, ...rest] = arr;

// In function params
function show({ name, age }) { ... }

Spread & Rest

// Spread: expand
const arr2 = [...arr1, 4, 5];
const obj2 = { ...obj1, age: 30 };
Math.max(...nums);

// Rest: collect remaining
const [head, ...tail] = [1,2,3];

Template Literals

const msg = `Hello ${name}, you are ${age} years old`;
// Multi-line — just press Enter inside backticks

Short-circuit

const val = a ?? 'default'; // null/undef
const val = a || 'fallback'; // falsy
user?.address?.city // optional chain

Arrays & Iteration

Core Methods

const nums = [1,2,3,4,5];

// map — transform each element
nums.map(x => x * 2);
// [2,4,6,8,10]

// filter — keep matching elements
nums.filter(x => x > 2);
// [3,4,5]

// reduce — accumulate to one value
nums.reduce((sum, x) => sum + x, 0);
// 15

// find / findIndex
nums.find(x => x > 3);     // 4
nums.findIndex(x => x===3);// 2

// some / every
nums.some(x => x > 4);   // true
nums.every(x => x > 0);  // true

// flat / flatMap
[[1],[2]].flat();          // [1,2]
nums.flatMap(x=>[x,x*2]);

Mutation Methods

arr.push(v)     // add end
arr.pop()       // remove end
arr.unshift(v)  // add start
arr.shift()     // remove start
arr.splice(i,n) // remove n from i
arr.sort((a,b)=>a-b) // numeric sort
arr.reverse()

Objects & Classes

Object Patterns

const name = 'Alice';
const user = {
  name,               // shorthand
  greet() { ... },   // method shorthand
  ['key']: 'dynamic'  // computed key
};

// Useful Object methods
Object.keys(user)    // ["name","greet"]
Object.values(user)  // values array
Object.entries(user) // [[k,v],...]
Object.assign({}, a, b) // merge
Object.freeze(obj)   // immutable

ES6 Classes

class Animal {
  #name; // private field
  constructor(name) {
    this.#name = name;
  }
  speak() {
    return `${this.#name} makes a sound`;
  }
}
class Dog extends Animal {
  speak() {
    return super.speak() + ' Woof!';
  }
}

Map & Set

const m = new Map();
m.set('a', 1); m.get('a');
const s = new Set([1,2,2,3]);
// Set {1, 2, 3} — unique values
const unique = [...new Set(arr)];

Async JavaScript

Promises

const p = new Promise((resolve, reject) => {
  // async work...
  resolve(data);  // success
  reject(error);   // failure
});
p.then(data => ...)
 .catch(err => ...)
 .finally(() => ...);

// Multiple promises
Promise.all([p1, p2])    // all succeed
Promise.race([p1, p2])   // first settles
Promise.allSettled([...])// all settle

Async / Await

async function fetchUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new Error(res.status);
    const data = await res.json();
    return data;
  } catch (err) {
    console.error('Error:', err);
  }
}

// Parallel with async/await
const [a, b] = await Promise.all([p1(), p2()]);

Event Loop

// Call Stack → Web APIs → Task Queue
// Microtasks (Promises) before macrotasks
// (setTimeout, setInterval)
setTimeout(() => /*macro*/, 0);
Promise.resolve().then(() => /*micro*/);

DOM & Browser

Selecting Elements

document.querySelector('#id');
document.querySelectorAll('.cls');
document.getElementById('id');

Manipulation

el.textContent = 'text';
el.innerHTML = '<b>bold</b>';
el.classList.add('active');
el.classList.toggle('hidden');
el.setAttribute('href', '/path');
el.style.color = 'red';
parent.appendChild(child);
el.remove();

Events

el.addEventListener('click', (e) => {
  e.preventDefault();  // stop default
  e.stopPropagation(); // stop bubble
  e.target;             // element clicked
});
// Event delegation — listen on parent:
list.addEventListener('click', e => {
  if (e.target.matches('li')) { ... }
});

Storage & Utilities

localStorage.setItem('k', JSON.stringify(v));
localStorage.getItem('k');
// Debounce: wait until user stops typing
const debounce = (fn, ms) => {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), ms);
  };
};

2 React + Redux

Components & JSX

Functional Component

// Always start with capital letter
function UserCard({ name, age, onClick }) {
  return (
    <div className="card">
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <button onClick={onClick}>Click</button>
    </div>
  );
}
// Export
export default UserCard;
export { UserCard }; // named

JSX Rules

// 1. Single root element (or Fragment)
return (
  <>
    <A /><B />
  </>
);
// 2. className, not class
// 3. htmlFor, not for
// 4. Self-close empty tags <br />
// 5. JS in {curly braces}
// 6. Conditional rendering
{isLoggedIn && <Dashboard />}
{isLoggedIn ? <A /> : <B />}
// 7. Lists must have key prop
{items.map(item => (
  <li key={item.id}>{item.name}</li>
))}

Core Hooks

useState

const [count, setCount] = useState(0);
// Never mutate state directly!
setCount(count + 1);
setCount(prev => prev + 1);
// Objects: spread to update
setUser(prev => ({ ...prev, age: 30 }));
// Arrays: use map/filter (no mutation)
setItems(prev => [...prev, newItem]);
setItems(prev => prev.filter(i=>i.id!==id));

useEffect

// Runs after every render
useEffect(() => { ... });

// Runs once (mount)
useEffect(() => { ... }, []);

// Runs when dep changes
useEffect(() => { ... }, [userId]);

// Cleanup (unmount / before re-run)
useEffect(() => {
  const timer = setInterval(fn, 1000);
  return () => clearInterval(timer);
}, []);

// Fetch data pattern
useEffect(() => {
  async function load() {
    const data = await fetchUsers();
    setUsers(data);
  }
  load();
}, []);

More Hooks & Patterns

useContext

// 1. Create context
const ThemeCtx = React.createContext(null);

// 2. Provide at top level
function App() {
  const [theme, setTheme] = useState('dark');
  return (
    <ThemeCtx.Provider value={{ theme, setTheme }}>
      <Page />
    </ThemeCtx.Provider>
  );
}
// 3. Consume anywhere
const { theme } = useContext(ThemeCtx);

useReducer

const reducer = (state, action) => {
  switch (action.type) {
    case 'INC': return { ...state, count: state.count+1 };
    default: return state;
  }
};
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: 'INC' });

useMemo & useCallback

// useMemo — memoize expensive value
const sorted = useMemo(
  () => items.sort(...),
  [items]
);
// useCallback — memoize function ref
const handleClick = useCallback(
  () => { ... },
  [dep]
);
// React.memo — skip re-render if props same
export default React.memo(MyComponent);

Redux Toolkit

Setup Store

// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

export const store = configureStore({
  reducer: { counter: counterReducer },
});

// index.js — wrap App
<Provider store={store}>
  <App />
</Provider>

Create Slice

// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment(state) { state.value++; },
    decrement(state) { state.value--; },
    addBy(state, action) {
      state.value += action.payload;
    },
  },
});
export const { increment, decrement, addBy } =
  counterSlice.actions;
export default counterSlice.reducer;

Use in Component

import { useSelector, useDispatch } from 'react-redux';
import { increment, addBy } from './counterSlice';

function Counter() {
  const count = useSelector(s => s.counter.value);
  const dispatch = useDispatch();
  return (
    <button onClick={() => dispatch(addBy(5))}>
      {count}
    </button>
  );
}

Lifecycle → useEffect Map

// componentDidMount  (run once on mount)
useEffect(() => { fetchData(); }, []);

// componentDidUpdate  (run on dep change)
useEffect(() => { updateTitle(); }, [title]);

// componentWillUnmount  (cleanup)
useEffect(() => {
  const sub = subscribe();
  return () => unsubscribe(sub);
}, []);

// getDerivedStateFromProps — useMemo / derive from props
const fullName = useMemo(
  () => `${firstName} ${lastName}`,
  [firstName, lastName]
);

// Error Boundary (still class-based in React 18)
class ErrBoundary extends React.Component {
  componentDidCatch(error, info) { ... }
  render() {
    return this.state.hasError
      ? <h1>Something broke</h1>
      : this.props.children;
  }
}

Custom Hook + Async Redux (Thunk)

Custom Hook — reusable logic

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  useEffect(() => {
    fetch(url)
      .then(r => r.json()).then(setData)
      .catch(setError).finally(() => setLoading(false));
  }, [url]);
  return { data, loading, error };
}
// Usage: const { data, loading } = useFetch('/api/users');

createAsyncThunk (async Redux)

export const fetchUsers = createAsyncThunk(
  'users/fetch',
  async (id, { rejectWithValue }) => {
    try { return await api.getUsers(id); }
    catch (e) { return rejectWithValue(e.message); }
  }
);
// In slice extraReducers:
builder
  .addCase(fetchUsers.pending, s => { s.loading=true; })
  .addCase(fetchUsers.fulfilled, (s, a) => {
    s.loading = false; s.users = a.payload;
  })
  .addCase(fetchUsers.rejected, (s, a) => {
    s.error = a.payload;
  });

3 SQL — PostgreSQL

Core Query Structure

-- Execution order (not write order):
-- FROM → JOIN → WHERE → GROUP BY
-- → HAVING → SELECT → ORDER BY → LIMIT

SELECT   u.name, COUNT(o.id) AS order_count
FROM     users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE    u.active = true
GROUP BY u.id, u.name
HAVING   COUNT(o.id) > 2
ORDER BY order_count DESC
LIMIT    10;

WHERE Conditions

WHERE age BETWEEN 18 AND 65
WHERE country IN ('VN', 'SG', 'TH')
WHERE name LIKE 'A%'     -- starts with A
WHERE name ILIKE '%alice%'-- case-insensitive
WHERE email IS NULL
WHERE email IS NOT NULL
WHERE NOT (status = 'inactive')

CASE WHEN

SELECT name,
  CASE
    WHEN score >= 90 THEN 'A'
    WHEN score >= 80 THEN 'B'
    ELSE 'C'
  END AS grade
FROM students;

JOIN Patterns

Join Types

-- INNER JOIN: only matching rows
SELECT * FROM users u
INNER JOIN orders o ON u.id = o.user_id;

-- LEFT JOIN: all from left + matched right
SELECT u.name, o.id
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

-- Unmatched rows only (anti-join)
WHERE o.id IS NULL;

-- Self-join: compare rows in same table
SELECT e.name, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

GROUP BY Patterns

-- Count per group
SELECT country, COUNT(*) FROM users
GROUP BY country;

-- Find duplicates
SELECT email, COUNT(*)
FROM users GROUP BY email
HAVING COUNT(*) > 1;

-- Aggregates
COUNT(*) · SUM(col) · AVG(col)
MIN(col) · MAX(col)
COUNT(DISTINCT col)

Subqueries & Window Fns

Subquery Patterns

-- IN subquery
SELECT * FROM products
WHERE category_id IN (
  SELECT id FROM categories
  WHERE active = true
);

-- Scalar subquery
SELECT name,
  (SELECT COUNT(*) FROM orders o
   WHERE o.user_id = u.id) AS total
FROM users u;

-- EXISTS
SELECT * FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o
  WHERE o.user_id = u.id
);

Window Functions

-- Syntax: fn() OVER (PARTITION BY .. ORDER BY ..)

-- Row number per partition
SELECT name, score,
  ROW_NUMBER() OVER (
    PARTITION BY dept_id
    ORDER BY score DESC
  ) AS rank
FROM employees;

-- Running total
SUM(amount) OVER (ORDER BY created_at)

-- Lag/Lead (previous/next row)
LAG(salary, 1) OVER (ORDER BY hire_date)

Data Modification & CTEs

INSERT / UPDATE / DELETE

-- INSERT
INSERT INTO users (name, email, age)
VALUES ('Alice', 'a@b.com', 28)
RETURNING id; -- get back inserted ID

-- INSERT multiple rows
INSERT INTO tags (name) VALUES
  ('js'), ('react'), ('sql');

-- UPDATE
UPDATE users
SET age = 29, updated_at = NOW()
WHERE id = 5;

-- DELETE
DELETE FROM sessions
WHERE expired_at < NOW();

CTE (WITH clause)

-- Readable, reusable named subquery
WITH top_users AS (
  SELECT user_id, SUM(amount) AS total
  FROM orders
  GROUP BY user_id
  HAVING SUM(amount) > 1000
)
SELECT u.name, t.total
FROM users u
JOIN top_users t ON u.id = t.user_id;

Top N per Group

-- Top 1 score per department
WITH ranked AS (
  SELECT *, ROW_NUMBER() OVER (
    PARTITION BY dept_id
    ORDER BY score DESC
  ) AS rn
  FROM employees
)
SELECT * FROM ranked WHERE rn = 1;

4 Linux CLI

File & Dir Management

Navigation

pwdPrint current directory
ls -laList all (hidden + details)
cd /pathChange to absolute path
cd ..Go up one level
cd ~Go to home directory
cd -Go to previous directory

Create / Copy / Move / Delete

mkdir -p a/b/cCreate nested dirs
touch file.txtCreate empty file
cp src dstCopy file
cp -r src/ dst/Copy directory
mv old newMove / rename
rm file.txtDelete file
rm -rf dir/Force delete dir

View File Content

cat filePrint entire file
less filePaginated view (q to quit)
head -n 20 fileFirst 20 lines
tail -f log.txtFollow log in real-time
wc -l fileCount lines

Search & Text Processing

find — locate files

# Find by name
find . -name "*.js"

# Find by type (f=file, d=dir)
find . -type f -name "*.log"

# Find & execute command
find . -name "*.tmp" -delete

# Find modified in last 7 days
find . -mtime -7

grep — search in files

# Basic search
grep "error" app.log

# Case-insensitive
grep -i "error" app.log

# Recursive in all files
grep -r "TODO" src/

# Show line number
grep -n "function" app.js

# Invert (lines NOT matching)
grep -v "debug" app.log

# With regex
grep -E "error|warn" app.log

sort, uniq, cut, awk

cat f | sort | uniq -c  # count unique
cut -d',' -f1 data.csv   # 1st CSV column
awk '{print $2}' file     # 2nd field
sed 's/foo/bar/g' file   # replace text

Permissions & Processes

chmod — change permissions

# Octal: r=4, w=2, x=1
# 755 = rwxr-xr-x
# 644 = rw-r--r--
chmod 755 script.sh   # owner=rwx group=r-x other=r-x
chmod +x script.sh    # add execute for all
chmod -R 644 dir/     # recursive
chown user:group file # change owner

Process Management

ps auxList all processes
top / htopLive process monitor
kill -9 PIDForce kill by PID
pkill nodeKill by name
jobsList background jobs
cmd &Run in background
fg %1Bring job 1 to foreground
Ctrl+CInterrupt current process
Ctrl+ZSuspend process

Disk & Memory

df -hDisk usage (human readable)
du -sh dir/Directory size
free -hRAM usage

Networking & Shell Tips

Network Commands

ip addr showShow IP addresses
ping google.comTest connectivity
curl -X GET urlHTTP GET request
curl -X POST -d '{}' urlPOST with data
wget urlDownload file
ssh user@hostSSH into server
scp f user@h:/pathSecure copy to server
netstat -tulpnOpen ports

Pipes & Redirection

# Pipe: send output to next command
cat f.txt | grep "err" | wc -l

# Redirect output to file
echo "hello" > out.txt    # overwrite
echo "hello" >> out.txt   # append
cmd 2> err.log            # stderr to file
cmd > out.txt 2>&1        # both to file

Variables & Scripts

#!/bin/bash
NAME="World"
echo "Hello $NAME"

# Conditionals
if [ -f "file.txt" ]; then
  echo "exists"
fi

# Loop
for f in *.js; do
  echo "$f"
done

Common Flags Memory Aid

-h  human-readable   -r  recursive
-f  force            -v  verbose
-i  interactive      -n  dry run / line no
-a  all (hidden)     -l  long format

5 NestJS
Request Lifecycle — Interview Gold
Request Middleware Guard Interceptor (pre) Pipe Controller Service Interceptor (post) Exception Filter Response

Architecture & Bootstrap

Philosophy

TypeScript-firstBuilt with & for TypeScript
ModularFeature modules, shared modules
DI containerInversion of Control built-in
Express / FastifyPluggable HTTP adapters

main.ts — bootstrap

import { NestFactory } from '@nestjs/core';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true, transform: true
  }));
  app.enableCors();
  app.enableShutdownHooks();
  await app.listen(3000);
}
bootstrap();

Key Decorators

@Module({ imports, providers, controllers, exports })
@Controller('users')    // route prefix
@Injectable()           // DI provider
@Get() @Post() @Put(':id') @Delete(':id')
@Param('id') @Body() @Query() @Headers()

Modules & Dependency Injection

Module Structure

@Module({
  imports:     [TypeOrmModule, AuthModule],
  controllers: [UsersController],
  providers:   [UsersService],
  exports:     [UsersService],  // share
})
export class UsersModule {}

DI Scopes

SingletonOne instance per app (default)
RequestNew instance per HTTP request
TransientNew instance per injection

Custom Providers

// inject a constant
{ provide: 'CFG', useValue: { port: 3000 } }

// swap class implementation
{ provide: Logger, useClass: WinstonLogger }

// async factory with injection
{ provide: 'DB',
  useFactory: async (cfg) => createConn(cfg),
  inject: [ConfigService] }

Request Pipeline

Guard — authorization

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(ctx: ExecutionContext) {
    const req = ctx.switchToHttp().getRequest();
    return validateToken(req.headers.authorization);
  }
}

Pipe — validate & transform

app.useGlobalPipes(new ValidationPipe({
  whitelist: true,            // strip extra
  forbidNonWhitelisted: true, // throw on extra
  transform: true,            // auto-cast
}));

Exception Filter

@Catch(HttpException)
export class HttpExFilter implements ExceptionFilter {
  catch(ex: HttpException, host: ArgumentsHost) {
    const res = host.switchToHttp().getResponse();
    res.status(ex.getStatus()).json({ message: ex.message });
  }
}

DTO & Validation

DTO with class-validator

import { IsEmail, IsNotEmpty, MinLength,
  IsOptional } from 'class-validator';

export class CreateUserDto {
  @IsNotEmpty() name:     string;
  @IsEmail()    email:    string;
  @MinLength(8) password: string;
  @IsOptional() role?:   string;
}

class-transformer

import { Exclude, Expose } from 'class-transformer';

export class UserResponseDto {
  @Expose()  id:       number;
  @Expose()  email:    string;
  @Exclude() password: string; // hidden
}

Common Validators

@IsString()Must be a string
@IsNumber()Must be a number
@IsEnum(Role)Must be enum value
@IsArray()Must be an array
@IsUUID()Must be valid UUID

Auth & Security

JWT + Passport Strategy

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey:    process.env.JWT_SECRET,
    });
  }
  async validate(payload) {
    return { id: payload.sub, email: payload.email };
  }
}

RBAC — Roles Guard

// decorator
export const Roles = (...r) => SetMetadata('roles', r);

// guard reads metadata via Reflector
const req = this.reflector.get('roles', ctx.getHandler());

Security Best Practices

HelmetHTTP security headers
ThrottlerModuleRate limiting (DDoS)
bcryptPassword hash ≥10 rounds
CORSapp.enableCors({ origin })

Data Access & Testing

ORM Comparison

PrismaSchema-first, type-safe client, migrations
TypeORMEntity decorators, @InjectRepository
MongooseMongoDB, schema + model pattern
DrizzleSQL-first, lightweight, type-safe

Repository Pattern

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private repo: Repository<User>
  ) {}
  findAll() { return this.repo.find(); }
}

Unit Testing

const module = await Test.createTestingModule({
  providers: [UsersService, {
    provide:  getRepositoryToken(User),
    useValue: { find: jest.fn() },
  }]
}).compile();

Advanced Features

Microservices & Transports

TCPSimple message passing between services
RedisPub/Sub pattern via Redis transport
gRPC + ProtobufHigh-performance binary RPC
Kafka / RabbitMQEvent streaming & message queues

GraphQL — code-first

@Resolver(() => User)
export class UsersResolver {
  @Query(() => [User])
  users() { return this.svc.findAll(); }

  @Mutation(() => User)
  createUser(@Args('input') i: CreateUserInput) {
    return this.svc.create(i);
  }
}

WebSocket Gateway

@WebSocketGateway({ cors: true })
export class EventsGateway {
  @WebSocketServer() server: Server;

  @SubscribeMessage('message')
  handle(@MessageBody() data: string) {
    this.server.emit('broadcast', data);
  }
}

Production & Scaling

Performance

Fastify adapter~2× throughput vs Express
SWC compiler10× faster than tsc build
Standalone appcreateApplicationContext()

Docker Multi-stage

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./ && RUN npm ci
COPY . . && RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/main"]

Observability

OpenTelemetryTraces, metrics, logs (OTLP)
Pino / WinstonStructured JSON logging
enableShutdownHooks()SIGTERM → graceful close
ConfigModule + Joi/ZodEnv validation at startup

6 TypeScript
Type Safety Compass — Interview Gold
any opt-out → avoid | unknown safe, must narrow | never impossible / exhaustive | type unions · aliases | interface declaration merging | generic <T> reusable + safe | strict: true always enable

Type System Fundamentals

Primitives & Special Types

string · number · booleanCore primitives
null · undefined · bigintOther primitives
anyOpt-out — avoid, use unknown
unknownSafe any — narrow before use
neverImpossible type · exhaustive checks
voidFunction returns undefined

as const & satisfies

// as const — freeze to literal type
const ROLES = ['admin', 'user'] as const;
type Role = typeof ROLES[number];
// 'admin' | 'user'

// satisfies — validate without widening
const cfg = { port: 3000 } satisfies Config;
cfg.port; // type: 3000 (not number)

Strict Mode — tsconfig.json

{
  "compilerOptions": {
    "strict": true,        // all safety flags
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext"
  }
}

Utility Types — Must Know

Structural

Partial<T>All properties optional
Required<T>All properties required
Readonly<T>All properties readonly
NonNullable<T>Remove null & undefined

Extraction

Pick<T, K>Keep listed keys
Omit<T, K>Remove listed keys
Record<K, V>Map from keys to values
Exclude<T, U>Remove union members
Extract<T, U>Keep matching members

Function & Async

ReturnType<F>Function return type
Parameters<F>Params as tuple
Awaited<T>Unwrap Promise (recursive)
type User = { id: number; name: string; pw: string };
type PublicUser  = Omit<User, 'pw'>;
type UpdateUser  = Partial<Pick<User, 'name'>>;
type UserMap     = Record<number, User>;

Generics & Type Narrowing

Generic Functions

// Constraint — T must have .length
function first<T extends { length: number }>(arr: T) {
  return arr.length;
}

// Type-safe property getter
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

Type Narrowing

// typeof — primitives
if (typeof val === 'string') val.toUpperCase();

// instanceof — classes
if (err instanceof Error) err.message;

// in — duck typing
if ('swim' in animal) animal.swim();

// Type predicate — custom guard
function isString(v: unknown): v is string {
  return typeof v === 'string';
}

keyof & typeof

type Keys = keyof User;           // 'id' | 'name' | 'pw'
type Cfg  = typeof config;         // infer from value
type Emails = User['email'];      // lookup type

Interfaces & Classes

type vs interface

interfaceDeclaration merging · extends · implements
typeUnions · intersections · primitives · tuples
// interface — extendable, mergeable
interface Animal { name: string }
interface Dog extends Animal { breed: string }

// type — unions (interface cannot)
type Status = 'active' | 'inactive';
type Admin  = User & { permissions: string[] };

Class Access Modifiers

class User {
  constructor(
    public  readonly id: number,
    private name: string,
    protected email: string,
  ) {}
}
// public — accessible anywhere
// private — class only (TS; use # for runtime)
// protected — class + subclasses
// readonly — set only in constructor

enum vs as const (prefer as const)

// as const — recommended, no runtime overhead
const STATUS = { Active: 'ACTIVE', Off: 'OFF' } as const;
type Status = typeof STATUS[keyof typeof STATUS];

Real-world Patterns

Discriminated Unions

type State =
  | { kind: 'loading' }
  | { kind: 'ok';    data: string[] }
  | { kind: 'error'; msg: string };

switch (state.kind) {
  case 'ok':    return state.data;
  case 'error': throw state.msg;
  default:      assertNever(state); // safe
}

Zod — Runtime + Compile-time

import { z } from 'zod';
const Schema = z.object({
  id:    z.number().positive(),
  email: z.string().email(),
});
type User = z.infer<typeof Schema>; // no duplication

const result = Schema.safeParse(data);
if (result.success) result.data.email; // typed

Branded Types

type Brand<T, B> = T & { readonly __brand: B };
type UserId    = Brand<number, 'UserId'>;
type ProductId = Brand<number, 'ProductId'>;
// getUser(productId) → ERROR! nominal safety

Advanced Types & Async

Mapped & Conditional Types

// Mapped — transform all keys
type Nullable<T> = { [K in keyof T]: T[K] | null };

// Conditional — ternary at type level
type IsStr<T> = T extends string ? 'yes' : 'no';

// infer — extract inner type
type Unwrap<T> = T extends Promise<infer R> ? R : T;
type Deep<T> = { [K in keyof T]: T[K] extends object
  ? Deep<T[K]> : T[K] };  // recursive

Async / Promise Typing

async function fetchUser(id: number): Promise<User> {
  const res = await fetch('/users/' + id);
  return res.json() as Promise<User>;
}

// Error handling — catch e: unknown
try { await fetchUser(1); }
catch (e: unknown) {
  if (e instanceof Error) e.message;
}

Build Tools

tsc --noEmitType check only (CI gate)
tsx / ts-nodeRun .ts directly (dev)
SWC / esbuild10x faster build (no type check)
tsupLibrary bundler — CJS + ESM + .d.ts
Node 22+ --experimental-strip-typesNo build step

7 DSA — JavaScript
Big-O Complexity — Know These Cold
O(1) Map/Set/Object lookup · push/pop · arithmetic | O(log n) binary search · BST avg · heap insert | O(n) scan · BFS/DFS · hash build | O(n log n) merge/quick sort · Array.sort | O(n²) nested loops · bubble/insertion sort | O(2ⁿ) backtracking · subset enumeration
Most Frequent Coding Patterns
Sliding Window Two Pointers HashMap / Freq Counter Binary Search BFS / DFS Prefix Sum Monotonic Stack Memo / DP Backtracking Fast & Slow Pointers

Linear Structures

Array — operation costs

push / popO(1) amortized
shift / unshiftO(n) — avoid in hot loops
splice / sliceO(n)
indexOf / includesO(n) linear scan
arr[i]O(1) random access

Stack — LIFO (array-based)

const stack = [];
stack.push(x);       // O(1)
stack.pop();        // O(1)
stack[stack.length-1]; // peek O(1)
// use: valid parentheses, monotonic stack

Queue — FIFO (optimized)

class Queue {
  #data = {}; #head = 0; #tail = 0;
  enqueue(v) { this.#data[this.#tail++] = v; }
  dequeue()  { return this.#data[this.#head++]; }
  size()     { return this.#tail - this.#head; }
}
// O(1) enqueue AND dequeue — use for BFS

Linked List node

class ListNode {
  constructor(val, next = null) {
    this.val = val; this.next = next;
  }
}
// key: reverse · cycle (fast+slow) · middle

Hash Structures — O(1) Power

Map vs Set vs Object

MapAny key · insertion order · .size
SetUnique values · O(1) has/add/delete
{}String/Symbol keys · prototype chain risk
// Frequency counter — char count
const freq = new Map();
for (const c of str)
  freq.set(c, (freq.get(c) ?? 0) + 1);

// Two sum — O(n)
function twoSum(nums, target) {
  const seen = new Map();
  for (let i = 0; i < nums.length; i++) {
    const comp = target - nums[i];
    if (seen.has(comp)) return [seen.get(comp), i];
    seen.set(nums[i], i);
  }
}

Sliding Window — O(n)

// Longest substring without repeating chars
let l = 0, best = 0;
const win = new Set();
for (let r = 0; r < s.length; r++) {
  while (win.has(s[r])) win.delete(s[l++]);
  win.add(s[r]);
  best = Math.max(best, r - l + 1);
}

Two Pointers — O(n)

// Container with most water
let l = 0, r = h.length - 1, best = 0;
while (l < r) {
  best = Math.max(best, Math.min(h[l],h[r])*(r-l));
  h[l] < h[r] ? l++ : r--;
}

Trees — Interview Favourite

TreeNode + DFS traversals

class TreeNode {
  constructor(val, left=null, right=null) {
    this.val=val; this.left=left; this.right=right;
  }
}
// Recursive DFS (pre-order)
function dfs(node) {
  if (!node) return;
  visit(node);          // pre-order
  dfs(node.left);
  // visit(node)         // in-order
  dfs(node.right);
  // visit(node)         // post-order
}

BFS — level order

function bfs(root) {
  const q = [root]; const res = [];
  while (q.length) {
    const n = q.length;
    const level = [];
    for (let i=0; i<n; i++) {
      const node = q.shift();
      level.push(node.val);
      if (node.left)  q.push(node.left);
      if (node.right) q.push(node.right);
    }
    res.push(level);
  }
  return res;
}

Classic tree problems

Max depth1 + max(dfs(L), dfs(R))
Diametermax(L+R) across all nodes
LCAbubble up when node = p or q
Path sumDFS subtract target at each node
Validate BSTpass [min, max] bounds in DFS

Graphs — BFS / DFS Mastery

Adjacency list (Map)

const graph = new Map();
function addEdge(u, v) {
  if (!graph.has(u)) graph.set(u, []);
  if (!graph.has(v)) graph.set(v, []);
  graph.get(u).push(v);
  graph.get(v).push(u); // undirected
}

BFS — shortest path / level order

function bfs(start) {
  const visited = new Set([start]);
  const queue = [start];
  while (queue.length) {
    const node = queue.shift();
    for (const nb of graph.get(node) ?? []) {
      if (!visited.has(nb)) {
        visited.add(nb); queue.push(nb);
      }
    }
  }
}

DFS — components / cycle

function dfs(node, visited) {
  visited.add(node);
  for (const nb of graph.get(node) ?? [])
    if (!visited.has(nb)) dfs(nb, visited);
}

Classic graph problems

Number of islandsBFS/DFS on grid, mark visited
Course scheduleTopological sort (Kahn's BFS)
Clone graphBFS + Map (original→clone)
Shortest pathBFS (unweighted) · Dijkstra (weighted)

Sorting & Binary Search

Array.sort() — Timsort O(n log n)

// Numeric sort — MUST provide comparator
arr.sort((a, b) => a - b);        // asc
arr.sort((a, b) => b - a);        // desc
// String sort (default = lexicographic)
arr.sort((a, b) => a.localeCompare(b));
// Gotcha: [10,9,2].sort() → [10,2,9] !

Binary Search template — O(log n)

function binarySearch(arr, target) {
  let lo = 0, hi = arr.length - 1;
  while (lo <= hi) {
    const mid = lo + Math.floor((hi-lo)/2);
    if      (arr[mid] === target) return mid;
    else if (arr[mid] < target)  lo = mid + 1;
    else                         hi = mid - 1;
  }
  return -1;
}
// Variants: first/last occurrence, rotated array

Merge sort — O(n log n), O(n) space

function mergeSort(arr) {
  if (arr.length <= 1) return arr;
  const mid = Math.floor(arr.length / 2);
  const L = mergeSort(arr.slice(0, mid));
  const R = mergeSort(arr.slice(mid));
  return merge(L, R); // merge two sorted halves
}

Prefix Sum — O(n) preprocess, O(1) query

const pre = [0];
for (const n of nums) pre.push(pre.at(-1) + n);
// sum[i..j] = pre[j+1] - pre[i]

Heaps & Union-Find

Min-heap (array-based)

parent(i)Math.floor((i-1)/2)
left(i)2*i + 1
right(i)2*i + 2
insertpush → bubble up O(log n)
extractMinswap root↔last → bubble down O(log n)
// Top-K elements with size-K min-heap
// Simulate: sort desc + slice(0, K) for interview
nums.sort((a,b)=>b-a).slice(0,k); // O(n log n)
// Real heap gives O(n log k) — mention trade-off

Union-Find — path compression

class UF {
  constructor(n) {
    this.p = Array.from({length:n}, (_,i)=>i);
    this.rank = new Array(n).fill(0);
  }
  find(x) {
    if (this.p[x] !== x) this.p[x] = this.find(this.p[x]);
    return this.p[x];       // path compression
  }
  union(x, y) {
    const [px,py] = [this.find(x), this.find(y)];
    if (px===py) return false;
    this.rank[px] >= this.rank[py]
      ? this.p[py]=px : this.p[px]=py;
    return true;
  }
}
// ≈ O(α(n)) per op · use: islands, connections

Dynamic Programming

Recognize DP problems

"min/max"Optimal substructure
"count ways"Overlapping subproblems
"is it possible"Boolean DP

Memoization — top-down

const memo = new Map();
function fib(n) {
  if (n <= 1) return n;
  if (memo.has(n)) return memo.get(n);
  const res = fib(n-1) + fib(n-2);
  memo.set(n, res); return res;
}

Tabulation — bottom-up

// Coin change — min coins to make amount
function coinChange(coins, amount) {
  const dp = new Array(amount+1).fill(Infinity);
  dp[0] = 0;
  for (let i=1; i<=amount; i++)
    for (const c of coins)
      if (c <= i) dp[i] = Math.min(dp[i], dp[i-c]+1);
  return dp[amount] === Infinity ? -1 : dp[amount];
}

Classic DP patterns

Climbing stairsdp[i] = dp[i-1] + dp[i-2]
House robberdp[i] = max(dp[i-1], dp[i-2]+nums[i])
LISdp[i] = max(dp[j]+1) for j<i, arr[j]<arr[i]
0/1 knapsack2D dp[i][w] = max(take, skip)
Edit distance2D dp[i][j] = ins/del/replace cost

Recursion & Backtracking

Recursion rules in JavaScript

Always define base casePrevents infinite recursion
V8 stack depth~10k frames — convert deep recursion to iterative
Time = work × callsfib naive = O(2ⁿ), memo = O(n)

Backtracking template

function backtrack(path, options) {
  if (isDone(path)) { result.push([...path]); return; }
  for (const opt of options) {
    if (!isValid(opt)) continue; // prune
    path.push(opt);              // choose
    backtrack(path, nextOptions); // explore
    path.pop();                   // unchoose
  }
}

Subsets — O(2ⁿ)

const res = [[]];
for (const n of nums)
  res.push(...res.map(sub => [...sub, n]));
return res;

Monotonic Stack — O(n)

// Next greater element
const res = new Array(nums.length).fill(-1);
const stack = []; // stores indices, decreasing
for (let i=0; i<nums.length; i++) {
  while (stack.length && nums[stack.at(-1)] < nums[i])
    res[stack.pop()] = nums[i];
  stack.push(i);
}

Fast & Slow Pointers — cycle detection

let slow = head, fast = head;
while (fast && fast.next) {
  slow = slow.next;
  fast = fast.next.next;
  if (slow === fast) return true; // cycle!
}

JS Tips & Interview Strategy

JS DSA gotchas

NaN !== NaNUse Number.isNaN(x) to check
-0 === 0Object.is(-0, 0) → false
[10,9,2].sort()"10","2","9" — must use (a,b)=>a-b
Integer overflowMAX_SAFE_INT = 2⁵³-1 · use BigInt
mod 10⁹+7(a * b) % 1_000_000_007

Performance tips

Avoid arr.shift()O(n) — use pointer or Queue class
Map over {}Any key type, no prototype pollution
[...arr] spreadO(n) copy — avoid in tight loops
Int32ArrayTyped array — faster numeric ops

Interview communication

State complexity first"This is O(n) time, O(1) space"
Brute → optimizeShow O(n²) then improve with HashMap
Edge casesEmpty input · single element · duplicates · negatives
Pattern recognitionName the pattern: "sliding window", "two pointers"

Useful JS utilities for DSA

Math.floor(n/2)      // integer division
n >> 1              // same — bit shift (faster)
Math.max(...arr)     // max of array
Array.from({length:n},(_,i)=>i) // [0..n-1]
str.split('').reverse().join('') // reverse str
Number.MAX_SAFE_INTEGER // 9007199254740991

8 Enterprise Infrastructure Architectures
Evolution Path — Scale with Confidence
MVP: Docker + Vercel Add RAG + Clerk + Stripe Mature: K8s + GitOps Enterprise: multi-LLM + OTel
Best Recommended Enterprise Stack 2026
Turborepo + pnpm Next.js + shadcn/ui NestJS + Prisma + Zod PostgreSQL + pgvector + Redis Vercel AI SDK + LiteLLM Clerk + Stripe + Resend Docker + K8s + GitOps
1 · Core Infrastructure

Monorepo — Turborepo + pnpm

Why monorepo

Turborepobuild caching — only rebuild changed packages
pnpm workspacesshared deps, symlinked node_modules
packages/sharedTS types, Zod schemas, utils — imported by all apps
turbo.jsonpipeline config — order + caching rules

Structure

apps/
  web/      ← Next.js frontend
  api/      ← NestJS backend
packages/
  shared/   ← types, Zod schemas, utils
  ui/       ← shadcn component library
  config/   ← ESLint, tsconfig presets

Key commands

# scaffold
pnpm create turbo@latest

# run only affected + deps
turbo run build --filter=api...

# add dep to one workspace
pnpm --filter=api add @nestjs/core

Docker Multi-stage + Node 22

Why multi-stage

Node 22 LTSlatest LTS (2024) — V8 11.8, native fetch, WebSocket
SWC compiler10-70× faster than tsc — used in NestJS + Next.js
alpine base~5 MB vs ~100 MB debian — smaller final image
.dockerignoreexclude node_modules, .git, dist to speed up build

Production Dockerfile

# ── Stage 1: build ──
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ── Stage 2: run ──
FROM node:22-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]

Kubernetes + Helm + Terraform

AWS EKS deployment flow

EKSmanaged Kubernetes on AWS — control plane handled by AWS
HelmK8s package manager — chart = versioned deployment template
TerraformIaC multi-cloud (HCL) — provision VPC, EKS, RDS
AWS CDKIaC in TypeScript — better DX for TypeScript teams

Deploy with Helm

# install/upgrade release
helm upgrade --install api ./charts/api \
  --set image.tag=v1.2.3 \
  --namespace prod \
  --create-namespace

Secrets & Config

AWS Secrets Managerstore DB passwords, API keys — auto-rotate
HashiCorp Vaultself-hosted secrets + dynamic credentials
@nestjs/configtyped env with Zod schema validation on startup
// validate env at startup with Zod
ConfigModule.forRoot({
  validate: (env) => EnvSchema.parse(env)
})
2 · AI Runtime Integration

Vercel AI SDK — Streaming & Tools

Core API

streamText()stream LLM response token-by-token to client
generateObject()structured JSON output with Zod schema validation
tool()define callable tools — the LLM decides when to call
useChat()React hook — handles messages, streaming, loading state

NestJS streaming example

import { streamText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await streamText({
  model: openai('gpt-4o'),
  system: 'You are a helpful assistant.',
  messages,
  tools: {
    search: tool({
      description: 'Search the knowledge base',
      parameters: z.object({ query: z.string() }),
      execute: async ({ query }) => await vectorSearch(query),
    }),
  },
});
return result.toDataStreamResponse();

RAG Pipeline — pgvector

RAG flow

1. Ingestchunk docs → embed with OpenAI/Voyage → store in pgvector
2. Queryembed user query → cosine similarity search → top-K chunks
3. Generateinject chunks into LLM prompt → get grounded answer
pgvectorPostgreSQL extension — vector(1536) column type

pgvector setup

-- enable extension
CREATE EXTENSION IF NOT EXISTS vector;

-- store embeddings
ALTER TABLE docs ADD COLUMN
  embedding vector(1536);

-- HNSW index for fast ANN search
CREATE INDEX ON docs USING hnsw
  (embedding vector_cosine_ops);

-- semantic search
SELECT id, content,
  1 - (embedding <=> '[...]'::vector) AS score
FROM docs
ORDER BY score DESC LIMIT 5;

LiteLLM — Multi-LLM Gateway

Why LiteLLM

Unified APIOpenAI-compatible for all LLMs — swap models in config
Fallbacksauto-switch to backup model if primary fails or rate-limits
Cost trackingper-call cost + usage logging per user/team
No vendor lock-inClaude → GPT-4o → Gemini without code changes

Config + usage

# litellm config.yaml
model_list:
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
  - model_name: claude-3-5-sonnet
    litellm_params:
      model: anthropic/claude-3-5-sonnet-20241022

router_settings:
  fallbacks:
    - gpt-4o: [claude-3-5-sonnet]
  num_retries: 3

LangChain.js / LangGraph

LangChain.jschains, prompts, memory, tool use — orchestration layer
LangGraphstateful multi-step agents — nodes + edges + state machine
3 · Observability & Security

OpenTelemetry Full Stack

Three pillars

Tracesrequest flow across services — spans with start/end/tags
MetricsPrometheus counters, histograms — visualized in Grafana
Logsstructured JSON — correlate with trace ID

NestJS setup — init before bootstrap

// instrumentation.ts — run BEFORE NestJS
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from
  '@opentelemetry/exporter-otlp-http';
import { getNodeAutoInstrumentations } from
  '@opentelemetry/auto-instrumentations-node';

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: 'http://otel-collector:4318/v1/traces',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();

AI observability

LangSmithLangChain-native — prompt tracing, run scores
LangFuseopen-source LLM obs — cost/call, latency, eval
Heliconeproxy-based — zero-code LLM logging

Resilience Patterns

Circuit breaker — states

CLOSEDnormal — requests flow through
OPENfailure threshold hit — fast-fail immediately
HALF_OPENprobe: allow one request to test if service recovered

Retry with backoff

import { retry, backoff, handleAll } from 'cockatiel';

const policy = retry(handleAll, {
  maxAttempts: 3,
  backoff: backoff.exponential({
    initialDelay: 100,
    maxDelay: 5000,
  }),
});

Graceful shutdown (NestJS)

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableShutdownHooks(); // listens for SIGTERM
  await app.listen(3000);
}
// implement OnApplicationShutdown in services
async onApplicationShutdown(signal: string) {
  await this.db.disconnect(); // drain connections
}

Security — AI + Enterprise

Prompt injection defense

function sanitizePrompt(input: string): string {
  const banned = /ignore previous|jailbreak|system:/i;
  if (banned.test(input))
    throw new ForbiddenException('Blocked');
  return input.slice(0, 2000); // length cap
}

Transport & API security

mTLSmutual TLS — both client + server present certificates
WAFAWS WAF / Cloudflare — block OWASP Top 10 at edge
API key rotationshort-lived keys, store in Secrets Manager, never in code
CORS + HelmetNestJS: app.use(helmet()) + CORS origin whitelist

NestJS security setup

import helmet from 'helmet';
app.use(helmet());
app.enableCors({ origin: ['https://myapp.com'] });
app.useGlobalPipes(new ValidationPipe({
  whitelist: true,   // strip unknown fields
  forbidNonWhitelisted: true,
}));
4 · Third-party & SaaS Integrations

Auth — Clerk Recommended

Options

ClerkSaaS + orgs + billing — fastest to ship
Auth.jsflexible + self-hosted — any OAuth provider
Auth0enterprise compliance — SOC2, HIPAA
Supabase AuthPostgres-native — great with Supabase stack

Clerk in NestJS

import { clerkMiddleware } from '@clerk/express';
app.use(clerkMiddleware());

@Get('me')
getMe(@Req() req) {
  return req.auth.userId; // Clerk userId
}

Stripe Webhooks in NestJS

Critical: verify signature

@Post('webhook')
@HttpCode(200)
async stripeWebhook(
  @Req() req: RawBodyRequest<Request>,
  @Headers('stripe-signature') sig: string,
) {
  const event = this.stripe.webhooks
    .constructEvent(req.rawBody, sig,
      process.env.STRIPE_WEBHOOK_SECRET);
  switch (event.type) {
    case 'checkout.session.completed':
      await this.provisionAccess(event.data);
      break;
  }
}

Key events

checkout.session.completedprovision access after payment
customer.subscription.updatedsync plan/limits
payment_intent.payment_failednotify + retry logic

Outbox Pattern

Problem solved

Events lost if app crashes between DB write and message publish. The Outbox pattern atomically stores events in the same DB transaction.

Outbox table

CREATE TABLE outbox (
  id          UUID PRIMARY KEY,
  event_type  TEXT,
  payload     JSONB,
  published_at TIMESTAMPTZ,
  created_at  TIMESTAMPTZ DEFAULT now()
);

-- Atomic: domain change + event in ONE tx
BEGIN;
  UPDATE orders SET status='paid' WHERE id=1;
  INSERT INTO outbox (event_type, payload)
    VALUES ('order.paid', '{"orderId":1}');
COMMIT;

Integration patterns

Webhook verifyalways check signature header
Idempotencydeduplicate by event ID — re-delivery is normal
Rate limitexponential backoff when 429 from third-party

PostHog + Resend

PostHog analytics

Product analyticsevents, funnels, retention — open-source
Feature flagsgradual rollouts without redeployment
Session replaywatch user interactions
import posthog from 'posthog-js';
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY);
if (posthog.isFeatureEnabled('new-ui')) {
  // show new UI
}

Resend — modern email

Resenddeveloper-first — React Email templates
SendGrid / Postmarkbattle-tested alternatives
TwilioSMS + WhatsApp
Pusher / Ablyreal-time WebSocket events
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_KEY);
await resend.emails.send({
  from: 'noreply@app.com',
  to: user.email,
  subject: 'Welcome!',
  react: WelcomeEmail({ name: user.name }),
});
5 · Data Layer — PostgreSQL + Redis + Vectors

Prisma vs Drizzle ORM

Comparison

Prismaschema.prisma → auto-migrate → type-safe Prisma Client
DrizzleTypeScript schema → SQL-like queries → lighter runtime
DX winnerPrisma — autocompletion, relations, Studio UI
Perf winnerDrizzle — no query engine, direct SQL driver
pgvectorboth support it (Prisma: unsupported type workaround)

Prisma quick reference

// find with relation
const user = await prisma.user.findUnique({
  where: { id },
  include: { posts: true },
});

// transaction
await prisma.$transaction([
  prisma.order.create({ data: orderData }),
  prisma.outbox.create({ data: eventData }),
]);

Redis — Multi-purpose Cache

Use cases

CacheGET/SET with TTL — avoid repeated DB queries
Sessionsstore JWT or session data — fast lookup by token
Pub/Subreal-time events between microservices
Rate limitingINCR + EXPIRE per IP/user per minute
BullMQ queuesjob queues — email, AI calls, report generation

NestJS cache + BullMQ

// Cache manager
@Inject(CACHE_MANAGER) private cache: Cache;
await this.cache.set('key', data, 3600);
const cached = await this.cache.get('key');

// BullMQ processor
@Processor('emails')
export class EmailWorker {
  @Process()
  async handle(job: Job) {
    await this.email.send(job.data);
  }
}

Messaging — Kafka vs SQS vs Streams

When to use each

Kafkahigh-throughput event streaming, consumer groups, replay
Redis Streamslightweight — same infra as your cache, ordered log
AWS SQSmanaged, serverless-friendly, at-least-once delivery
AWS SNSfan-out pub/sub — one message → many SQS subscribers

NestJS transports

// Kafka microservice
const app = await NestFactory
  .createMicroservice(AppModule, {
    transport: Transport.KAFKA,
    options: {
      client: { brokers: ['kafka:9092'] },
    },
  });

// Emit event
this.client.emit('order.created', payload);
Rule: Redis Streams for <10k msg/s · Kafka for high-throughput replay · SQS for serverless/simple queues
6 · Frontend — Next.js + React 19

Next.js App Router + React 19

Server vs Client Components

Server Componentdefault — async, zero JS to client, direct DB/API access
'use client'opt-in for interactive components (useState, events)
Server Actionsasync functions — mutations without API route
TanStack Queryclient-side cache + background sync + optimistic UI

Pattern: Server + Client split

// Server Component — runs on server
async function UserList() {
  const users = await db.user.findMany();
  return <ul>{users.map(u => <UserCard user={u}/>)}</ul>;
}

// Client Component — interactive
'use client';
export function LikeButton({ id }: { id: string }) {
  const [liked, setLiked] = useState(false);
  return <button onClick={() => setLiked(true)}>
    {liked ? '❤️' : '🤍'}
  </button>;
}

UI — Tailwind + shadcn/ui

Stack

shadcn/uicopy-paste components — you own the code, not a dep
Radix primitivesaccessible headless base (Dialog, Popover, etc.)
Tailwind CSSutility-first — no runtime CSS, JIT purging
cn() helpermerge Tailwind classes safely with clsx + twMerge

Setup + usage

# add component (copies to components/ui/)
npx shadcn-ui@latest add button dialog

// cn() — safe class merging
import { cn } from '@/lib/utils';
export function Button({ className, ...props }) {
  return <button
    className={cn('px-4 py-2 rounded', className)}
    {...props}
  />;
}

Vercel deployment

Edge functionsrun middleware at CDN edge — low latency globally
AI streamingVercel AI Gateway — caching + rate limiting for LLMs
Preview envsauto-deploy every PR branch for QA

AI Streaming in Next.js

useChat hook — Vercel AI SDK

'use client';
import { useChat } from 'ai/react';

export function Chat() {
  const { messages, input, handleSubmit,
          handleInputChange, isLoading } = useChat({
    api: '/api/chat',
  });

  return (
    <div>
      {messages.map(m => (
        <p key={m.id}>{m.role}: {m.content}</p>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange}/>
        <button type='submit' disabled={isLoading}>
          {isLoading ? '...' : 'Send'}
        </button>
      </form>
    </div>
  );
}

API route — streams from NestJS

// app/api/chat/route.ts
export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = await streamText({
    model: openai('gpt-4o'), messages,
  });
  return result.toDataStreamResponse();
}
7 · CI/CD & GitOps Deployment

GitHub Actions CI Pipeline

Monorepo CI with Turborepo

name: CI
on: [push, pull_request]

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
      - run: pnpm install --frozen-lockfile
      - run: pnpm turbo lint test build
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/org/api:\${{ github.sha }}

Key optimizations

--filter=api...only rebuild changed packages + deps
cache: turboremote Turbo cache — skip unchanged builds
concurrencycancel in-progress runs on new push

Argo CD — GitOps for K8s

GitOps principle

Git repo = single source of truth. Argo CD polls repo and reconciles K8s cluster state. git revert = instant rollback.

Application manifest

apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
  source:
    repoURL: https://github.com/org/infra
    path: charts/api
    targetRevision: main
  destination:
    namespace: production
  syncPolicy:
    automated:
      prune: true       # remove deleted resources
      selfHeal: true    # fix manual cluster changes

Progressive delivery

Blue-greenrun two identical envs, switch traffic instantly
Canaryroute % of traffic to new version, watch metrics
Feature flagsUnleash / LaunchDarkly — decouple deploy from release

AI-assisted Dev Workflow

Tools in 2026

GitHub Copilotinline completion + chat — integrated in VS Code/JetBrains
CursorAI-native editor — multi-file context, agentic edits
Claude CodeCLI agentic coding — full codebase context, terminal
Codiumauto-generate unit tests from code — CI-ready

AI-powered PR review

# .github/workflows/ai-review.yml
- uses: coderabbitai/ai-pr-reviewer@v2
  with:
    openai_light_model: gpt-4o-mini
    openai_heavy_model: gpt-4o
    review_simple_changes: false

Best practices

CLAUDE.mdproject context file — guides Claude Code in repo
Review AI outputalways verify generated code — security + correctness
Test generationCodium / Copilot for boilerplate test scaffolding
8 · Scaling, Multi-tenancy & AI Workloads

Multi-tenancy — RLS

Strategies

Shared schema + tenantIdsimplest — must filter every query
Schema-per-tenantfull isolation — migration complexity
DB-per-tenantstrongest isolation — highest cost
RLS (recommended)Postgres enforces row filtering automatically

PostgreSQL Row Level Security

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON orders
  USING (tenant_id =
    current_setting('app.tenant_id')::uuid);

-- Set per request in NestJS middleware
await prisma.\$executeRaw
  \`SET app.tenant_id = \${tenantId}\`;

Scaling AI Workloads

K8s patterns

HPAHorizontal Pod Autoscaler — scale on CPU or custom metrics
KEDAscale on external signals: queue depth, Redis list length
GPU nodesself-hosted LLMs (Llama, Mistral) on GPU node pool
Embedding cacheRedis TTL — avoid re-embedding same text repeatedly

KEDA — scale on BullMQ

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
spec:
  scaleTargetRef: { name: llm-worker }
  triggers:
    - type: redis
      metadata:
        listName: 'bull:llm-queue:wait'
        listLength: '10'  # 1 pod per 10 jobs

Serverless vs Kubernetes

Decision guide

Vercel / Netlifyfrontend + edge — zero ops, auto-scale, vendor managed
AWS Lambdaevent-driven, low-traffic, stateless — pay per invocation
EKS (K8s)long-running, stateful, AI workloads — full control
HybridNext.js on Vercel + NestJS on EKS — best of both

NestJS Lambda adapter

import { configure } from '@codegenie/serverless-express';
import { handler as expressHandler } from './app';

export const handler = configure({
  app: expressHandler,
});

File storage

AWS S3object storage — images, videos, exports
Presigned URLsclient uploads directly to S3 — skip your server
Cloudinaryimage transformation + CDN on-the-fly
UploadThingS3 abstraction — simple Next.js integration

9 Full-Stack Enterprise Tooling

SaaS Fast-Moving Stack (2026)

TypeScript · React · NestJS · 2026 — recommended starting point for new SaaS products
FrontendNext.js App Router, shadcn/ui + Tailwind, TanStack Query
BackendNestJS + Prisma, PostgreSQL + pgvector, Redis (BullMQ)
AuthClerk — orgs, MFA, social login, Stripe billing built-in
PaymentsStripe — subscriptions, webhooks, Radar fraud, customer portal
EmailResend + React Email templates (TypeScript SDK, great DX)
AnalyticsPostHog (events, flags, session replay) + Sentry (errors)
AI / RAGVercel AI SDK + pgvector + LiteLLM gateway
DeployVercel (Next.js) + Railway (NestJS + PostgreSQL + Redis)

Enterprise Heavy Stack & Trade-offs

AuthAuth0 — SAML/OIDC, MFA, compliance (SOC2/HIPAA), enterprise SSO
CMSContentful / Sanity — editorial workflows, localization, governance
CRMHubSpot / Salesforce — pipelines, email sequences, marketing
InfraAWS EKS + Terraform + Helm — full K8s control, CloudFront CDN
ObservabilityOTel + Prometheus + Grafana — full telemetry + alerting stack

Managed vs Self-hosted

Managed (DX)Vercel · Clerk · Contentful · Stripe · Resend · Pinecone · PostHog Cloud
Self-hosted (control)Payload · Strapi · n8n · PostHog OSS · pgvector · Railway · Render
Rule: don't self-host what you can pay $50/mo for at early stage — graduate when bill exceeds $500 or compliance demands it

1. Hosting & CDN

Vercelbest Next.js — Edge SSR, AI SDK native, zero-config, preview URLs per PR
Cloudflarebest price/perf — Workers, Pages, KV, D1 (SQLite edge), R2 (S3-compat)
AWS EKS/ECSenterprise — full K8s control, CloudFront CDN, complex 2-4 week setup
Railway/Rendermid-size — fast setup, DB included, auto-deploy from Git, good DX
Never run NestJS on Vercel Functions — cold starts kill DB connection pools and WebSockets

2. Databases & ORM

PostgreSQL + pgvectorprimary — relational + vector similarity, Row Level Security, no extra DB
Prismabest DX — schema-first, auto-migrations, great autocomplete, large binary
Drizzlelightweight — SQL-first TS, edge-compatible, faster runtime, no binary
Rediscaching · rate-limit · sessions · BullMQ jobs · pub/sub · sorted sets
File storageS3 (raw) · Cloudinary (transform+CDN) · UploadThing (TS-native S3)
-- pgvector: nearest neighbor search
SELECT id, content FROM docs
ORDER BY embedding <=> '[0.1,...]'::vector
LIMIT 5;

3. Auth & Identity

Clerkfastest SaaS DX — orgs, MFA, social login, Stripe billing, prebuilt UI components
Auth.jsOSS flexible — 60+ providers, Prisma/Drizzle adapters, self-hosted, no UI
Supabase AuthPostgres-native JWT + RLS, good if already on Supabase
Auth0enterprise SSO — SAML/OIDC, compliance, MFA rules, expensive at scale
// NestJS Clerk JWT guard
const payload = await
  clerkClient.verifyToken(token);
req.user = payload; // userId, org

4. Payments & Billing

Stripeindustry standard — subscriptions, webhooks, Radar fraud, hosted customer portal
Paddle / Lemon Squeezymerchant of record — handles global VAT/GST, simpler for indie devs

Stripe Webhook Security

// Raw body required for sig verify!
const event = stripe.webhooks
  .constructEvent(req.rawBody, sig, secret);
// Store event.id — idempotency key
// Outbox pattern: save → process async
// Never trust frontend redirect!

5. Communication

Resendmodern dev-first — React Email templates, TypeScript SDK, webhooks, great DX
SendGridmature — marketing + transactional, high deliverability, complex API design
Postmarkbest deliverability for transactional only (auth emails, invoices)
TwilioSMS, OTP (Verify API), WhatsApp Business, voice calls — one SDK
Pusher / Ablymanaged WebSocket — channels, presence (who's online), message history
// Resend + React Email (NestJS)
await resend.emails.send({
  from: 'no-reply@app.com',
  to: user.email,
  react: WelcomeEmail({ name }),
});

6. CMS & Content

Payload CMSTypeScript-first OSS — runs inside Next.js, self-hosted Postgres, REST + GraphQL auto-generated
Contentfulenterprise headless — GraphQL+REST, localization, workflow approvals, rich CDN
Sanity.ioreal-time collab — GROQ queries, custom Studio UI in React, hosted Content Lake
Strapiclassic OSS — self-hosted, plugin ecosystem, auto-generated admin panel
TS monorepo → Payload · Enterprise editorial team → Contentful · Real-time collab → Sanity · Quick OSS → Strapi

7. Analytics & Monitoring

PostHogOSS — product analytics, session replay, feature flags, A/B testing — self-host or cloud
Sentryerror tracking + performance — stack traces, source maps, slow endpoint detection
Mixpanel/Amplitudeadvanced behavioral — funnels, cohorts, retention analysis by segment
Complementary stack: PostHog (product behavior) + Sentry (errors) + OTel/Grafana (infra) — use all three
// PostHog feature flag (Next.js)
posthog.isFeatureEnabled('new-ui');
// Sentry (NestJS)
Sentry.init({ dsn, tracesSampleRate: 0.1 });

8. CRM & Automation

HubSpotall-in-one CRM — contacts, deals, email sequences, pipelines, free tier (1M contacts)
Zapiermanaged no-code — 6000+ app integrations, easy, expensive at volume
Make.comvisual flows — more powerful than Zapier, better value, branching logic
n8nself-hosted OSS — 400+ integrations, JS/TS code nodes, GDPR-safe, free
Pattern: NestJS webhook → create HubSpot contact on sign-up, trigger n8n onboarding sequence

9. AI & RAG Tools

Vercel AI SDKbest React/TS — useChat, streamText, tool calling, multi-provider (OpenAI/Anthropic/Gemini)
pgvectorstart here — vector search in Postgres, HNSW index, scales to ~5M vectors
Pineconemanaged vector DB — >5M vectors, sub-10ms, metadata filtering, $24+/mo
LiteLLMunified LLM gateway — single API for all providers, fallbacks, cost tracking
LangChain.jsorchestration — agents, chains, document loaders, LangSmith debugging
// Vercel AI SDK — stream + tools
const result = streamText({
  model: openai('gpt-4o'),
  messages,
  tools: { searchDB: tool({...}) },
});
return result.toDataStreamResponse();

10. Forms & Uploads

// React Hook Form + Zod
const schema = z.object({
  email: z.string().email(),
  pw:    z.string().min(8),
});
type T = z.infer<typeof schema>;
const form = useForm<T>({
  resolver: zodResolver(schema),
}); // same schema validates DTO!
zodResolverbridges RHF + Zod — one schema for form validation + TypeScript type
UploadThingTS-native S3 uploads — type-safe file routes, useUploadThing hook, Next.js native
Zod everywhereforms · API input pipe · env vars at startup · external API response parsing

11. DevOps & CI/CD

GitHub ActionsCI/CD — lint, test, build, Docker push, deploy triggers, Copilot PR summaries
Turborepo + pnpmmonorepo — parallel builds, remote cache (share across CI), affected-only runs
Docker + K8s + Terraformmulti-stage builds, EKS, Helm charts, Terraform IaC — full enterprise infra
Argo CDGitOps — declarative desired state in Git, sync loop, one-click rollback
# turbo.json — pipeline config
"build": { "dependsOn": ["^build"] }

# CI: build only changed packages
pnpm turbo build \
  --filter='...[origin/main]'

12. Developer Experience

shadcn/ui + Tailwindcopy-paste Radix primitives — own the components, dark mode, fully accessible
TanStack Queryserver state — useQuery/useMutation, cache, stale-while-revalidate, optimistic UI
Swagger / OpenAPI@nestjs/swagger auto-generates docs + client from DTO decorators
// TanStack Query + invalidation
const { data } = useQuery({
  queryKey: ['users'],
  queryFn: () => api.getUsers(),
  staleTime: 60_000,
});
// After mutation:
queryClient.invalidateQueries(
  { queryKey: ['users'] }
);
npx shadcn-ui@latest add button — copies component into your repo, you own it completely

10 Docker

Core Architecture

Client-Server Model

# docker run nginx  →  internal flow:
Docker CLI ──REST API──▶ dockerd
                              ↓
                        containerd
                              ↓
                           runc (OCI)
                     ┌────────┴────────┐
                namespaces         cgroups
          (pid/net/mnt/uts)    (CPU/mem/PID)

Linux Primitives

pid namespaceown PID 1, cannot see host processes
net namespaceown eth0, IP, routing table
mnt namespaceown filesystem tree (pivot_root)
cgroupsCPU/memory/PID limits — kernel-enforced

Image Layers (UnionFS / overlay2)

# Layers stack bottom → top (read-only):
  Layer N  ← COPY . .       (your code)
  Layer 3  ← RUN npm install (deps)
  Layer 2  ← COPY package.json .
  Layer 1  ← FROM node:18-alpine
+ Writable ← container adds at runtime (lost on rm)
Copy-on-Write: writes copy the file up to the writable layer. Shared layers = less disk. Cached layers = faster rebuilds.

Image Lifecycle

# build → tag → push → pull → run
docker build -t myapp:1.0 .
docker tag myapp:1.0 ghcr.io/user/myapp:1.0
docker push ghcr.io/user/myapp:1.0
docker pull ghcr.io/user/myapp:1.0
docker run -d -p 3000:3000 myapp:1.0

Dockerfile Mastery

Core Instructions

FROM node:18-alpine          # base image
WORKDIR /app                 # set & create dir
COPY package*.json ./        # dep files FIRST
RUN npm ci --only=production # cached layer
COPY . .                     # source (changes often)
ENV NODE_ENV=production      # baked into image
ARG BUILD_VER                # build-time only, not in image
EXPOSE 3000                  # docs only — not publish
ENTRYPOINT ["node"]          # fixed executable
CMD ["dist/main.js"]         # default args (overridable)

CMD vs ENTRYPOINT ★ interview classic

ENTRYPOINTfixed binary — not replaced by docker run args
CMDdefault args — docker run img other.js overrides it
exec form["node","main.js"] — PID 1 = node, SIGTERM works ✓
shell formnode main.js — PID 1 = /bin/sh, signals broken ✗

Layer Caching Strategy

# ❌ Bad — npm install reruns on every code change:
COPY . .
RUN npm ci

# ✅ Good — cached unless package.json changes:
COPY package*.json ./
RUN npm ci --only=production   # stable cached layer
COPY . .                       # only this rebuilds

Multi-Stage Build

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine AS runtime
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
USER node
CMD ["node", "dist/main.js"]
# 1GB builder → ~150MB runtime image
.dockerignore — always exclude: node_modules/ .git/ .env dist/ coverage/ *.log — prevents bloated build context.

Essential Commands

Build & Run

docker build -t myapp:1.0 .
docker build --no-cache -t myapp .
docker build --build-arg KEY=val .
docker build -f Dockerfile.prod .

docker run -d \
  -p 3000:3000 \
  -e DATABASE_URL=postgres://db:5432/mydb \
  -v pgdata:/var/lib/postgresql/data \
  --memory 512m --cpus 0.5 \
  --rm --name api myapp:1.0

Inspect & Debug

docker ps                   # running
docker ps -a                # all (incl stopped)
docker logs -f myapp        # stream live logs
docker logs --tail 50 myapp
docker exec -it myapp sh    # enter container
docker exec myapp env       # print env vars
docker inspect myapp        # full JSON metadata
docker inspect myapp \
  --format='{{.State.ExitCode}}'
docker stats                # live CPU/mem/net

Exit Codes

0success / clean exit
1app error — check docker logs
137OOMKilled — increase --memory
143SIGTERM received (graceful stop)

Cleanup

docker rm -f myapp          # force remove running
docker rmi myapp:1.0
docker volume rm pgdata
docker system prune         # stopped + dangling
docker system prune -a      # + all unused images
docker system prune -a --volumes
docker system df            # disk usage per type

Docker Compose

Full-Stack Example

version: '3.9'
services:
  api:
    build: .
    ports: ['3000:3000']
    environment:
      DATABASE_URL: postgres://db:5432/mydb
      REDIS_URL: redis://redis:6379
    depends_on:
      db: { condition: service_healthy }
    networks: [app-net]

  db:
    image: postgres:16-alpine
    volumes: [pgdata:/var/lib/postgresql/data]
    environment: { POSTGRES_PASSWORD: secret }
    healthcheck:
      test: ['CMD-SHELL','pg_isready -U postgres']
      interval: 5s
      timeout: 3s
      retries: 5
    networks: [app-net]

  redis:
    image: redis:7-alpine
    networks: [app-net]

volumes: { pgdata: }
networks: { app-net: }

Dev vs Prod Overrides

# override.yml (auto-loaded — dev only)
services:
  api:
    build: .
    volumes: [./src:/app/src]  # hot reload
    environment: { NODE_ENV: development }

# prod.yml (explicit — production)
services:
  api:
    restart: always
    environment: { NODE_ENV: production }

# docker-compose -f dc.yml -f dc.prod.yml up
Secrets: use .env + ${VAR} substitution. Never hardcode secrets in docker-compose.yml — it lives in git.

Networking & Volumes

Network Drivers

bridgedefault — custom bridge adds auto DNS by service name
hostshares host network stack — no port mapping, max perf
overlaymulti-host (Docker Swarm)
nonefully isolated — no networking

DNS & Port Mapping

# Service name = hostname (custom networks only):
DATABASE_URL: postgres://db:5432/mydb  # 'db' ✓
REDIS_URL:    redis://redis:6379       # 'redis' ✓

# Port mapping:
docker run -p 3000:3000 myapp      # host:container
docker run -p 8080:3000 myapp      # remap
docker run -p 127.0.0.1:3000:3000  # localhost only
# EXPOSE = docs only. -p = actually publishes.

Volumes & Storage ★ interview classic

Named volumeprod — Docker-managed, persists after rm
Bind mountdev — host path → container, hot-reload
tmpfsin-memory only, never on disk, ephemeral
# Named volume (production):
docker run -v pgdata:/var/lib/postgresql/data postgres

# Bind mount (dev hot-reload):
docker run -v $(pwd)/src:/app/src myapp

# Backup a named volume:
docker run --rm \
  -v pgdata:/src:ro -v $(pwd):/bak \
  alpine tar czf /bak/pgdata.tar.gz -C /src .

Security & Hardening

Non-Root User (always in prod)

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
USER node               # node user pre-exists in alpine
EXPOSE 3000
CMD ["node", "dist/main.js"]

# Runtime hardening:
docker run \
  --cap-drop ALL \
  --read-only \
  --security-opt no-new-privileges \
  myapp

Minimal Base Images

node:18~1GB · full Debian · large attack surface
node:18-alpine~120MB · minimal OS · recommended
distroless/nodejs18~90MB · no shell · most hardened

Image Scanning

docker scout cves myapp:latest
trivy image --severity CRITICAL myapp:latest
trivy image --exit-code 1 myapp  # fail CI on CVE

Resource Limits (cgroups)

docker run \
  --memory 512m \       # OOMKill if exceeded
  --memory-swap 512m \  # no swap
  --cpus 0.5 \          # 50% of one core
  --pids-limit 50 \     # prevent fork bombs
  myapp
Pin base image by digest for reproducible builds: FROM node:18-alpine@sha256:abc123...

BuildKit & Advanced

Secret Mounts (never in layers)

# Dockerfile:
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci

# Build command:
docker build --secret id=npm_token,src=.npmrc .

# SSH forwarding (private git repos):
RUN --mount=type=ssh git clone git@github.com:org/repo
docker build --ssh default .

# npm cache between builds:
RUN --mount=type=cache,target=/root/.npm \
    npm ci

Zombie Process Fix

# Node as PID 1 ignores SIGTERM → docker stop hangs

# Fix 1: --init flag (auto-adds tini):
docker run --init myapp
# compose: add  init: true  to service

# Fix 2: Explicit tini in Dockerfile:
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/main.js"]
tini as PID 1: forwards SIGTERM to child (graceful shutdown) · reaps zombie processes · exits with child's exit code.

Sidecar Pattern

services:
  app:
    image: myapi
    volumes: [logs:/var/log/app]
  log-shipper:             # ← sidecar
    image: fluent/fluent-bit
    volumes: [logs:/var/log/app:ro]
volumes: { logs: }

Swarm vs Kubernetes

Docker Swarmbuilt-in, Compose syntax, simple — small teams
KubernetesHPA, GitOps, service mesh — enterprise scale

CI/CD & Registry

GitHub Actions Pipeline

- uses: docker/setup-buildx-action@v3

- uses: docker/login-action@v3
  with:
    registry: ghcr.io
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/build-push-action@v5
  with:
    push: true
    tags: |
      ghcr.io/${{ github.repository }}:latest
      ghcr.io/${{ github.repository }}:${{ github.sha }}
    cache-from: type=registry,ref=.../cache
    cache-to:   type=registry,ref=.../cache,mode=max

Image Tagging Strategy

:latest❌ avoid in prod — mutable, no rollback possible
:git-sha✅ immutable, traceable, precise rollback
:v1.2.3✅ semver for releases — human readable

Cloud Deployment

GCP Cloud Runserverless, auto-scale to zero, simplest ops
AWS ECS Fargatemanaged, no servers, AWS-native
AWS EKS / GKEmanaged Kubernetes, enterprise scale
Railway / Renderpush Dockerfile → URL — small projects
Enable ECR tag immutability (IMMUTABLE) — prevents accidental tag overwrite in production.

Developer Learning Path

# Level 1: Containerize a NestJS app
# Level 2: Compose with PostgreSQL + Redis
# Level 3: Multi-stage builds → <200MB image
# Level 4: Deploy to AWS ECS/EKS + CI/CD

Interview Scenarios

Container vs VM

Containershared kernel · namespaces · ms startup · MBs
VMfull OS · hypervisor · min startup · GBs · stronger isolation

Giant Image (2GB) Fix

# Diagnose:
docker history myapp:latest   # find big layers
# Fix checklist:
# 1. node:18-alpine instead of node:18
# 2. .dockerignore: node_modules/ .git/ dist/
# 3. Multi-stage (devDeps stay in builder)
# 4. npm ci --only=production in runtime
# Result: 2GB → ~150MB

Secrets in Containers

# ❌ Never — visible in docker history:
ENV JWT_SECRET=supersecret

# ✅ Runtime — inject from external source:
docker run --env-file .env myapp   # .env not in git

# ✅ Build-time — BuildKit secret mount:
RUN --mount=type=secret,id=token \
    TOKEN=$(cat /run/secrets/token) npm ci

Debug: Container Starts Then Crashes

docker logs myapp                # read the error
docker inspect myapp \
  --format='{{.State.ExitCode}}' # 137 = OOMKilled
docker run -it \
  --entrypoint sh myapp          # poke around
docker run --memory 1g myapp     # fix OOM
Full-stack compose: Next.js (frontend) + NestJS (backend) + PostgreSQL (db) + Redis (cache) — all managed by docker-compose.yml.

11 Next.js — App Router · React 19 · 2026

Rendering Strategies

When to Use Each

SSRper-request HTML — personalized, always fresh, dynamic
SSGbuild-time HTML — ultra-fast, CDN-cached, no server cost
ISRstatic + revalidate window — best of SSG + freshness
CSRbrowser-only — dashboards, TanStack Query, user-specific
PPR ★static shell CDN instant + dynamic holes stream in (v15+)

Code Patterns

// SSR — force fresh per request:
export const dynamic = 'force-dynamic'
async function Page() { const data = await fetch(url) }

// ISR — revalidate every hour:
const data = await fetch(url, { next: { revalidate: 3600 } })

// SSG + dynamic routes:
export async function generateStaticParams() {
  return [{ id: '1' }, { id: '2' }]
}

// PPR (Next.js 15):
export const experimental_ppr = true
// Wrap dynamic parts in <Suspense> — rest is static
PPR = static shell from CDN instantly + dynamic islands stream in. Eliminates SSR vs SSG tradeoff entirely.

App Router Architecture

File Conventions

layout.tsxshared UI, persists across route changes
page.tsxroute content — makes route publicly accessible
loading.tsxautomatic Suspense fallback for the segment
error.tsxerror boundary — must be Client Component
not-found.tsx404 UI for the segment
route.tsAPI route handler (GET, POST, PATCH, DELETE)

Routing Patterns

app/
├── layout.tsx              # root layout (wraps all)
├── page.tsx                # /
├── blog/[slug]/page.tsx    # /blog/:slug
├── docs/[...slug]/page.tsx # /docs/a/b/c (catch-all)
├── (auth)/login/page.tsx   # /login (group, no URL segment)
├── (auth)/register/page.tsx # /register
├── @modal/page.tsx         # parallel route slot
└── shop/(..)cart/page.tsx  # intercepted route
Route groups (auth) share a layout without appearing in URL. Use for multi-layout apps (marketing vs dashboard).

Nested Layouts

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children
}: { children: React.ReactNode }) {
  return (
    <div>
      <Sidebar />      // persists — never re-renders
      <main>{children}</main>
    </div>
  )
}

Server vs Client Components

Decision Table ★ core concept

Server (default)async data, DB/ORM, no useState, no events, no browser API
'use client'useState/useEffect, onClick, browser APIs — only when needed

Composition Pattern

// ❌ "use client" at top pollutes entire subtree:
'use client'
export default function Page() {
  const data = await db.query()  // ✗ can't do this
}

// ✅ Push 'use client' to leaf — tree stays server:
// page.tsx (server component):
async function Page() {
  const data = await db.query()       // ✓ server only
  return <ClientButton data={data} />  // serialize → client
}

// ClientButton.tsx:
'use client'
export function ClientButton({ data }) {
  const [open, setOpen] = useState(false)
  ...
}

// Pass server component as children — stays server ✓:
<ClientWrapper><ServerComponent /></ClientWrapper>
Boundary rule: 'use client' marks the boundary — not "runs on client only." Everything in the subtree below becomes a Client Component.

Data Fetching & Caching

Fetch Patterns in Server Components

// Cached (ISR — revalidate every hour):
const data = await fetch(url, {
  next: { revalidate: 3600 }
})

// No cache (SSR — fresh per request):
const data = await fetch(url, { cache: 'no-store' })

// Tagged for on-demand revalidation:
const data = await fetch(url, {
  next: { tags: ['posts'] }
})

// Parallel fetches — no waterfall:
const [user, posts] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/posts').then(r => r.json()),
])

4 Caching Layers ★ deep dive

1. Request Memoizationsame fetch URL in one render → deduplicated (React)
2. Data Cachepersistent across requests, server-side (Next.js)
3. Full Route Cachepre-rendered HTML+RSC payload on disk (Next.js)
4. Router Cacheclient-side prefetch cache in browser (Next.js)

On-Demand Revalidation

// In Server Action or Route Handler:
revalidatePath('/blog')        // purge all blog routes
revalidatePath('/blog/[slug]', 'page')
revalidateTag('posts')         // purge tagged fetches

Server Actions & Mutations

Basic Server Action

// actions.ts
'use server'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  // Validate with Zod:
  const parsed = schema.safeParse({ title })
  if (!parsed.success) return { error: parsed.error }
  await db.post.create({ data: { title } })
  revalidatePath('/blog')
  redirect('/blog')
}

Form Action (progressive enhancement)

// page.tsx — works without JS!
<form action={createPost}>
  <input name="title" required />
  <button type="submit">Create</button>
</form>

useActionState + useOptimistic

'use client'
import { useActionState, useOptimistic } from 'react'

const [state, action, pending] = useActionState(
  createPost, null
)
// pending = true while action runs

const [optimisticPosts, addOptimistic] = useOptimistic(posts)

async function handleAdd(formData: FormData) {
  addOptimistic({ title: formData.get('title'), pending: true })
  await createPost(formData)     // rolls back on error
}
Server Actions replace API routes for mutations. No /api endpoint needed — call directly from components. Progressive enhancement built-in.

Streaming & Suspense

loading.tsx — Automatic Suspense

// app/dashboard/loading.tsx
export default function Loading() {
  return <DashboardSkeleton />  // shows instantly
}
// Wraps page.tsx in <Suspense> automatically

Granular Suspense (recommended)

import { Suspense } from 'react'

async function Page() {
  return (
    <div>
      <StaticHeader />            // renders immediately
      <Suspense fallback={<Skeleton />}>
        <SlowUserProfile />       // streams when ready
      </Suspense>
      <Suspense fallback={<Skeleton />}>
        <SlowFeed />              // streams in parallel
      </Suspense>
    </div>
  )
}
// No waterfall — both slow components fetch in parallel

PPR — Partial Pre-Rendering

// next.config.ts:
experimental: { ppr: true }

// page.tsx:
export const experimental_ppr = true

async function Page() {
  return (
    <>
      <StaticNav />           // CDN edge — instant
      <Suspense fallback={<Spinner />}>
        <DynamicFeed />       // streams after
      </Suspense>
    </>
  )
}
// SSR waits for ALL data
// PPR: static shell NOW + dynamic streams IN

Performance

next/image — Core Web Vitals

import Image from 'next/image'

// Fixed size (width+height required):
<Image src="/hero.jpg" width={800} height={400}
  alt="..." />

// Above-the-fold hero (priority = no lazy):
<Image src="/hero.jpg" fill priority
  sizes="100vw" alt="..." />

// Responsive:
<Image src="/photo.jpg" fill
  sizes="(max-width: 768px) 100vw, 50vw"
  alt="..." />
// Auto: WebP/AVIF · lazy load · CLS=0 · blur placeholder

next/font — Zero Layout Shift

import { Inter } from 'next/font/google'
const inter = Inter({
  subsets: ['latin'],
  variable: '--font-inter',
  display: 'swap',
})
// Self-hosted — no external request, zero CLS ✓

Dynamic Imports + next/script

import dynamic from 'next/dynamic'

const Chart = dynamic(() => import('./Chart'), {
  loading: () => <Skeleton />,
  ssr: false,       // client-only, skip SSR
})
const Modal = dynamic(() => import('./Modal'))  // lazy load
beforeInteractivecritical (consent) — blocks render
afterInteractiveanalytics — loads after hydration
lazyOnloadchat widgets, ads — lowest priority
Turbopack: next dev --turbo — 10× faster HMR (Rust-based bundler, stable in 2026 — replaces Webpack).

Middleware & API Routes

middleware.ts — Runs at Edge

import { NextRequest, NextResponse } from 'next/server'

export function middleware(req: NextRequest) {
  const token = req.cookies.get('auth-token')?.value
  if (!token) {
    return NextResponse.redirect(
      new URL('/login', req.url)
    )
  }
  return NextResponse.next()
}

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*'],
}
// Uses: auth redirects · geo routing · A/B tests · CSP

Route Handlers (app/api)

// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(req: NextRequest) {
  const posts = await db.post.findMany()
  return NextResponse.json(posts)
}

export async function POST(req: NextRequest) {
  const body = await req.json()
  const post = await db.post.create({ data: body })
  return NextResponse.json(post, { status: 201 })
}

Route Handler vs Server Action

Route Handlerwebhooks, third-party callbacks, REST API for mobile
Server Actionin-app mutations, form submissions, no /api needed
BFF pattern: Next.js server aggregates multiple microservice calls → one optimized response to client. Next.js → NestJS for complex domain logic.

State & Ecosystem

TanStack Query (server state)

'use client'
const { data, isPending } = useQuery({
  queryKey: ['posts'],
  queryFn: () => fetch('/api/posts').then(r => r.json()),
  staleTime: 60_000,
})
const { mutate } = useMutation({
  mutationFn: (data) => fetch('/api/posts', {
    method: 'POST', body: JSON.stringify(data)
  }),
  onSuccess: () => qc.invalidateQueries({ queryKey: ['posts'] }),
})

Zustand + URL State

// Zustand (global client state):
const useStore = create<State>((set) => ({
  count: 0,
  increment: () => set((s) => ({ count: s.count + 1 })),
}))
// No Provider, no hydration mismatch ✓

// URL state (nuqs — shareable filters):
import { useQueryState } from 'nuqs'
const [search, setSearch] = useQueryState('q')
// /products?q=shoes — shareable, back-button works

Auth + AI + CMS

ClerkSaaS DX — currentUser() in RSC, middleware protect
Auth.jsOSS self-hosted — auth() in RSC, all providers
Vercel AI SDKuseChat/useCompletion — streaming AI Server Actions
CMS + ISRSanity/Payload webhook → revalidateTag('posts')
React Compiler (stable 2026): auto-memoizes all components — no more useMemo/useCallback needed for performance.
Junior Developer Daily Essentials · JavaScript · React + Redux · SQL · Linux · NestJS · TypeScript · DSA · Enterprise Infra Architecture · Full-Stack Enterprise Tooling · Docker · Next.js · 2026