Full Stack Engineering
Full Stack Engineering
Day 6–7 · The whole loop
A forward deployed engineer ships features end-to-end. You will design a schema in the morning, write a backend route at lunch, build the React component in the afternoon, and ship it from a customer's WiFi by evening. There is no "front-end team" to throw it over to.
This chapter is the spine of the bootcamp - the part you'll come back to. We're going to keep it opinionated: one stack, taught well, that you can become productive in by end of Day 7.
Sub-chapter deep-dives
This primer is the spine; two sub-chapters split the stack into its halves. Do both - a forward deployed engineer owns the whole slice:
| # | Sub-chapter | What it covers |
|---|---|---|
| 1 | Frontend | The browser, React, Next.js App Router, Core Web Vitals, the design-quality bar |
| 2 | Backend | HTTP deeply, REST design, auth, validation, resilience, observability |
Outline
- The web request lifecycle - browser → DNS → CDN → server → DB → back
- HTTP, properly - methods, status codes, headers, cookies, CORS
- REST and beyond - designing resourceful APIs; when GraphQL/RPC make sense
- Backend: Node + TypeScript (and where Python fits)
- Frontend: React + Next.js App Router
- State management - server state vs UI state; React Query vs Zustand vs context
- Styling - Tailwind, design tokens, accessibility
- Auth - sessions, JWTs, OAuth, why "auth is hard" is true
- Deployment - Docker, Vercel, fly.io, EC2 - picking right
- The full slice - building a feature from schema to UI
101 Primer
What actually happens when a user clicks
[Browser]
│ 1. DNS resolves welzin.ai → IP
│ 2. TLS handshake
│ 3. GET / HTTP/2
▼
[CDN / Vercel edge] ← may serve cached HTML/JS
│ 4. Forwards to origin if dynamic
▼
[Next.js server]
│ 5. Runs your React Server Component
│ 6. Queries Postgres for data
│ 7. Streams HTML back
▼
[Browser]
│ 8. Parses HTML, requests JS bundles
│ 9. Hydrates React on the client
│ 10. Subsequent navigations are client-side
Understanding this pipeline tells you where to fix slow pages: CDN cache miss? DB query slow? Bundle too big? Hydration blocked?
HTTP, the part interviewers ignore
You will encounter all of these in your first month:
| Status | Means |
|---|---|
200 OK | great |
201 Created | use this when POST creates something |
204 No Content | success, nothing to return |
301/302 | redirect (permanent / temporary) |
304 Not Modified | client cache is fine, don't re-send |
400 Bad Request | client's fault, malformed |
401 Unauthorized | not logged in |
403 Forbidden | logged in but not allowed |
404 Not Found | doesn't exist (or you don't get to know) |
409 Conflict | concurrent write, e.g. version mismatch |
422 Unprocessable Entity | valid syntax, invalid semantics |
429 Too Many Requests | rate-limited |
500 / 502 / 503 / 504 | server problem |
Return the right code. 200 { "error": "..." } is an anti-pattern.
REST in one screen
A resource is a noun. The verb is the HTTP method.
GET /api/users → list users
GET /api/users/42 → fetch user 42
POST /api/users → create a user (body: JSON)
PATCH /api/users/42 → partial update
PUT /api/users/42 → replace (rarely what you want)
DELETE /api/users/42 → delete
- Use plural nouns.
- Use sub-resources for relationships:
GET /api/users/42/orders. - Pagination via
?page=2&limit=20or cursor:?after=<opaque>. - Never put verbs in the URL (
/api/getUser).
Backend: Node + TypeScript with Hono or Express
Minimal Hono server:
import { Hono } from 'hono'
import { z } from 'zod'
const app = new Hono()
const CreateUser = z.object({
email: z.string().email(),
name: z.string().min(1),
})
app.get('/api/users/:id', async (c) => {
const id = Number(c.req.param('id'))
const user = await db.user.findUnique({ where: { id } })
return user ? c.json(user) : c.json({ error: 'not found' }, 404)
})
app.post('/api/users', async (c) => {
const body = CreateUser.parse(await c.req.json())
const user = await db.user.create({ data: body })
return c.json(user, 201)
})
export default app
Three habits to build:
- Validate every input with Zod (or equivalent). Trust nothing from the wire.
- Type your DB layer. Prisma, Drizzle, or
sqlx. Strings of SQL with manualanyreturns are how you create incidents. - Errors are first-class. A consistent error shape (
{ error: { code, message, details? } }) makes the frontend's life ten times easier.
Python is fine too
For ML-adjacent work, prefer Python with FastAPI. Same Zod-shaped validation via Pydantic. Same opinions.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class CreateUser(BaseModel):
email: str
name: str
@app.post("/api/users", status_code=201)
def create_user(body: CreateUser):
return db.users.create(**body.model_dump())
Frontend: React + Next.js App Router
The default Welzin frontend stack:
- Next.js 15+ App Router for routing and SSR.
- TypeScript everywhere.
anyis a code smell. - Server Components for data fetching; Client Components when you need state or browser APIs.
- React Query (TanStack Query) for client-side server state.
- Zustand for genuinely local UI state that crosses components.
- Tailwind v4 for styling. No styled-components, no CSS-in-JS, no separate stylesheets per component.
A typical component:
'use client'
import { useQuery } from '@tanstack/react-query'
export function UserCard({ id }: { id: number }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', id],
queryFn: () => fetch(`/api/users/${id}`).then(r => r.json()),
})
if (isLoading) return <Skeleton />
if (error) return <ErrorState onRetry={() => location.reload()} />
return (
<article className="rounded-lg border p-4">
<h3 className="text-lg font-medium">{data.name}</h3>
<p className="text-sm text-zinc-500">{data.email}</p>
</article>
)
}
State management, demystified
There are two kinds of state:
- Server state - what's on the server. Cached, refetched, possibly stale. Use React Query.
- UI state - modals open/closed, form drafts, theme. Lives in components or Zustand.
Treating server data as UI state (sticking it in useState, manually syncing) is the cause of 70% of frontend bugs. Use a server-state library; don't roll it yourself.
Auth - the short, honest version
- Session-based auth (cookie holding a session ID, session in DB/Redis) - boring, robust, the right default for first-party web apps.
- JWTs - useful for stateless API auth across services. Annoying to revoke. Don't store them in
localStorageif the page is exposed to your own user input (XSS = full account compromise). - OAuth / SSO - use a library (NextAuth, Auth.js, Clerk, WorkOS). Do not write OAuth from scratch.
- Passwords: hash with
argon2idorbcrypt. Never store plaintext. Never email a password.
If a customer asks for SSO/SAML, that's WorkOS or similar. The cost is justified.
Deployment, picking right
| App shape | Use |
|---|---|
| Marketing site, dashboard, anything Next.js | Vercel |
| Long-running Node/Python service | fly.io or Render |
| Stateful workload, custom kernel | EC2 + Cockpit (see DevOps chapter) |
| Background jobs | a dedicated worker + queue (BullMQ, Celery, SQS) |
Premature Kubernetes is the most expensive mistake a small team can make. Stay on a PaaS until you have a named reason to leave.
The full slice - what we'll build on Day 7
By end of Day 7 you'll have built and deployed:
A "Customer Notes" app. List + detail page. Auth via email magic link. Postgres-backed. One LLM-powered feature (auto-summarise a note). Deployed to Vercel with a live URL.
That single project exercises every concept in this chapter.
Hands-on Checkpoints
- Read every HTTP status code in the table above without looking it up.
- Build a Hono (or FastAPI) server with
GET,POST,PATCH,DELETEfor anotesresource. Validate inputs. - Wire it to Postgres via Prisma (or SQLAlchemy).
- Build a Next.js client that lists notes, opens detail, edits inline. Use React Query.
- Add auth (NextAuth or Clerk, your call).
- Deploy to Vercel with a Postgres on Neon or Supabase.
- Share the live URL in the team channel.
Further reading
- The Modern JavaScript Tutorial - when JS itself feels shaky
- React docs (new) - actually good
- Next.js App Router docs
- TanStack Query docs
- web.dev/learn - for browser/network fundamentals
Welzin opinion: Pick boring, well-documented tools and master them. A team of five engineers shipping in Next.js + Postgres + Prisma + Tailwind will beat a team of fifteen each using their favourite stack. Boredom is a feature.