This template project is part of Sourdough, a set of JavaScript templates that were originally developed at Northeastern for their Software Engineering class in spring of 2026.
The functional content of this project is a minimal "transcript service" that
registers students by assigning them an ID and then lets course grades be
added and queried. Everything in the ./src directory can be deleted to
create a true empty project.
The base project configuration follows a philosophy of "minimalism, mostly." Project configuration should be minimal and have a bias towards implicit defaults. Deviations from this principle should be justified and documented (here or elsewhere). The project should work on the latest long-term supported version of Node (this is v24 as of mid-2026).
Notable exceptions to this principle:
-
.gitignoretakes a kitchen-sink approach and should freely accept additions. (For example, if a student accidentally checks in a file that could have been ignored, it makes sense to add that file here.) -
The ESLint configuration is a maximalist attempt at keeping new TypeScript programmers on the rails in a complicated codebase, and also giving them a sense of working inside style conventions of a project that may differ from their own.
-
If we can have all project variants using the exact same
tsconfig.jsonfile oreslint.config.mjsfile by adding a bit of cruft to the base configuration, that's a reasonable trade. Things are going to inevitably get copy-pasted, and so the fewer copies of configuration files there are, the better. Necessary changes should be minimal, clean diffs, for example when we want to have server code that can rely on node's definitions while shared code cannot.This is why the
.vscode/settings.jsonapplies Prettier to html and css files even though that's not relevant to the base project, and whyeslint.config.mjsincludes React's Rules of Hooks despite most of the project variants not including any React code.
This sets up a set of commands that projects should consistently support, when appropriate:
npm run checkruns TypeScriptnpm run lintruns ESLint, andnpm run lint:fixruns eslint with the--fixoptionnpm run prettierchecks formatting, andnpm run prettier:fixwrites formatted files back to disknpm run testruns Vitest tests and reports coveragenpm run devstarts a development server or watch processnpm run buildprepares the project for production-style deploymentnpm startruns the project in production mode
These are tested by github actions in .github/workflows/main.yml.
This base project has an opinionated ESLint configuration that relies on typed linting. The ESLint configuration makes some assumptions about project structure:
- Frontend code is code that lives in
./frontendor./client, and optionally uses React and JSX. This code is subject to different linter rules. - Test code lives in a
**/testsdirectory OR has a*.spec.ts(x)or a*.test.ts(x)filename. Tests can use devDependencies, unlike other code. - Config files all have
*.config.mjsfilenames (vite, vitest, playwright, and eslint all can follow this convention). Config files can also import devDependencies, like tests but unlike other code. - Most everything should be registered as
error. Warnings don't fail CI checks. Exceptions should have a documented reason. Notable exceptions where we either turn on warnings or leave the warn default in place:no-consoleiswarnbecause no-console regularly gets turned off by line or file specific rules: we want to discourage excessiveno-consoleuse but it is more like the admonition to not check in commented-out code: it's mostly a problem when done excessively and it's easy to check in visual inspection.prettieriswarnbecause red squigglies forprettierare especially distracting and we can check for prettier failures in CI separately.simple-import-sort/importsiswarn— it's a property much like prettier in that it's a mechanical fix and the red squigglies are extremely distracting. It's also no huge loss if this doesn't get flagged in CI.- We do not override the default setting of
warnforreact-hooks/exhaustive-depsin the default configuration. This rule makes the (horrible) suggestion to remove the dependency array, and people breaking their projects by blindly following that suggestion would be a bad outcome.
TypeScript is configured with options that support
type stripping.
Beyond this, the TypeScript configuration enables
noFallthroughCasesInSwitch, noImplicitOverride, noImplicitReturns, and
noUncheckedIndexedAccess, which are linter-like properties that don't seem
to be well-supported by typed linting in ESLint.
Matt Pocock's cheat sheet is a reasonable source for more on minimal typescript configuration.
The .prettierrc file is intended to use some reasonable defaults. A
.vscode/settings.json file is added to encourage Visual Studio Code to treat
Prettier as the default formatter for javascript, typescript, json, css, and
html files even if a students' global configuration uses other defaults.
The .prettierrc, .gitattributes, and .vscode/settings.json files
conspire to generally force projects to use \n file endings instead of
Windows-style \r\n line endings (LF instead of CRLF).
The various Sourdough starters live in a single git repository as a series of Git branches that build off of one another.
base, the base configurationexpress, adds an Express server and API testsfullstack, adds a Vite frontend (+ Playwright end-to-end tests) for a simple client/server setupfullstack-react, makes the Vite frontend use Reactworkspaces, uses NPM workspaces to mediate validation and types that can be productively shared between the frontend and backend
The main and fullstack-react branches should coincide, so you can use
GitHub to fork the full-stack React project by just forking this repository.
For other branches, or if you don't want to deal with the weirdness of being a forked GitHub project, you'll want to follow a pattern like this, replacing the three bits in square brackets as needed:
git init
git branch -M main
git remote add upstream git@github.com:robsimmons/sourdough.git
git remote add origin git@github.com:[MY_USERNAME]/[MY_PROJECT].git
git fetch upstream
git merge upstream/[THE_STARTER_YOU_WISH_TO_FORK]
git push -u origin main