Portfolio Manifest — Prepared 2026-08-20

Terrence Daniels

Full-Stack Engineer

An independently modernized fork of the RealWorld Conduit example app — a Medium-style publishing platform (CRUD, JWT auth, pagination) rebuilt one file at a time rather than copied over wholesale, with every dependency brought up to its current latest and every real bug found via a real failing test, not read twice and assumed fine. Backend complete and independently runnable; frontend in progress.

71phases shipped
214/214backend tests passing
16/16frontend services + context done
27real bugs found & fixed
0open CodeQL alerts
100%TypeScript, both sides

Why this project

A dated example app, modernized for real

RealWorld's Conduit is a well-known "same app, N stacks" demo — a useful common ground, but the reference implementations age: pinned dependencies, an unmaintained CommonJS backend, no TypeScript, no CI, no tests. Rather than fork it wholesale, this repo rebuilds it file by file, upgrading every dependency and pattern to current latest as each file goes in, with the reasoning for every real decision written down as it happened — not reconstructed afterward.

React 19 Vite + SWC TypeScript Express 5 Sequelize 6 PostgreSQL Vitest GitHub Actions CodeQL

One backend, soft auth by default

Five route groups, four models, one middleware that never rejects a request for missing auth

verifyToken runs in front of nearly every route, but it's not a gate: no Authorization header just means the request proceeds anonymously — each controller decides for itself whether that's acceptable. Real migrations are the only schema source of truth; nothing patches the live schema at boot anymore.

Deliberate deviations, not defaults

bcrypt cost factor bumped 10→12 (current OWASP guidance). JWTs now expire after 7 days and verification is pinned to algorithms: ["HS256"] explicitly — the source issued tokens that never expired. A global rate limiter now sits in front of every /api route, closing 16 real CodeQL alerts at once instead of patching each router individually.

Real migrations, not sync-at-boot

The source relied on sequelize.sync({ alter: true }) at boot, which silently patched the live schema — exactly how two missing foreign-key columns went unnoticed there. This repo's migrations are authored from scratch and are the only thing that creates schema now. See the data model.

Real bugs, not just features

Found by a real failing test or a real CI run, not by reading the source twice

Every entry below was caught by something that actually ran — a test written to fail on the bug first, a CI job, or CodeQL — and every fix was re-verified by reverting it and watching the test fail again before being called done.

01
models/User.js — wrong foreign key on Comments
The Comments association was copy-pasted from Article.js and never corrected to the right key. Caught and proven by a test written to fail on the original bug and pass on the fix.
high · correctness
02
migrations — missing FK columns, masked by sync-at-boot
create-article/create-comment never created the userId/articleId columns their models' associations need — only worked in the source because alter: true patched the live schema at boot. Every migration now cross-checks its column shape against the real model in its own test.
high · schema integrity
03
helper/jwt.js — tokens that never expired
No expiry was ever set, and jwt.verify() accepted any algorithm the token claimed. Fixed to a 7-day expiry and algorithms: ["HS256"] pinned explicitly — both proven by deliberately reverting each and watching the test catch the regression.
high · security
04
controllers/user.ts — always-true password guard
The password-hashing guard evaluated true on every profile update, crashing any update that didn't touch the password field at all — the single most severe bug found in the backend build.
high · correctness
05
controllers/users.ts — login issued tokens with username: undefined
The signed JWT payload never actually included the username it claimed to — every logged-in session silently carried a broken claim until this was caught and fixed.
high · correctness
06
middleware/authentication.js — double next()
A missing return let request handling fall through to a second next() call after an error response had already been sent. Fixed and proven by reverting the fix and watching the regression reappear.
medium · correctness
07
middleware/errorHandler.js — raw error.message leaked to clients
The generic 500 handler forwarded the real error.message straight to API clients on any unexpected error — a real information-disclosure gap, masked behind a generic message instead.
medium · security
08
controllers/comments.ts — deleteComment skipped an ownership check
Unlike its sibling functions in the same file, deleteComment never checked that the comment being deleted actually belonged to the article named in the URL slug. Fixed to match the file's own established pattern.
medium · correctness
09
index.ts — catch-all 404 only matched GET
app.get("/*any", ...) meant an unmatched POST/PUT/ DELETE fell through to Express's default HTML 404 instead of this API's own JSON shape. Fixed to app.all(...), verified by reverting and watching a POST come back with an empty body first.
medium · API consistency
10
CodeQL — 16× missing rate limiting, 1× insecure randomness
Every authorizing route handler across 5 route files lacked a rate limiter; the article seeder used Math.random() to pick a random author. Fixed with one global express-rate-limit instance ahead of every route mount, and crypto.randomInt() in the seeder.
medium · security
11
frontend/errorHandler.test.ts — a mock that passed for the wrong reason
The source's mock built error.response from the Fetch API's Response class, which has no .data field — its assertion passed because of an unrelated crash, not the code path it claimed to test. Fixed with a real axios-shaped mock; proven by deliberately breaking the extraction and watching all 5 cases fail first.
medium · test correctness
12
frontend/getComments.ts — missing headers, wrong data on every fetch
The service never accepted a headers param at all, unlike its siblings, even though the backend's GET-comments route changes response shape based on whether a token is sent — comments were always fetched as if the user were logged out.
medium · correctness
13
frontend/AuthContext.tsx — a failed refresh could silently wipe the logged-in user
getUser({ headers }).then((loggedUser) => setAuthState(...)) had no guard against an undefined resolution — errorHandler can swallow certain errors and resolve nothing, which would have overwritten a valid logged-in user with undefined. TypeScript's User | undefined return type on getUser forced the fix.
medium · correctness

14 more, smaller findings — a seeder that bypassed bcryptHash() entirely, a missing tagList default, a missing await on setAuthor, two message-formatting bugs, a FeedContext.tsx click handler reading e.target instead of e.currentTarget, and more — are logged in full in todo.md's phase-by-phase record, not summarized away here.

Verifying it actually works

Live CI, not a static screenshot

Every push runs the full suite — ESLint, Prettier, TypeScript, and Vitest — plus a separate CodeQL security scan. The badges at the top of the repository's README are live, not decorative.

214/214 backend, real SQLite behind it

Controller and route tests run against a genuine in-memory SQLite Sequelize instance running the real models — not hand-stubbed mocks. See the testing strategy.

Checkable, not asserted

Every number on this page is reproducible from the repository's own todo.md phase log, commit history, and live CI runs.

Judgment calls

Where the interesting decisions actually happened

The frontend is being built bottom-up, the same way the backend was — leaf modules (helpers, shared types, API services) before anything that depends on them. That layer is done: all 16 service modules, then AuthContext/FeedContext, both memoized only after a review caught their Provider values causing unnecessary re-renders on every consumer. The components layer is next, sequenced the same way — a full dependency graph across the source's ~24 components was mapped up front so zero-dependency leaves get built before anything that composes them. One real architectural choice got decided rather than left open: TanStack Query versus the source's hand-rolled fetch/loading/error pattern, settled in favor of the latter after checking that the reference app itself has no query-library dependency at all. A process gap surfaced twice — the backend's convention of a real GitHub Issue and board card per file quietly lapsed for a handful of frontend files, not once but twice — caught both times from a stale sub-issue count, backfilled both times, and now holding. A newer, still-open gap: the 20 frontend files built so far have no behavioral tests, unlike the backend where every file has one — disclosed and tracked on the board rather than quietly deferred.