Start from a clean checkout
A setup guide is useful only if it works for someone who does not have your machine's hidden state. Begin in a new directory from the intended repository revision. Read the project README and runtime files before installing anything. Do not copy an existing dependency folder from another project.
Record the runtime and package manager
Use the runtime version declared by the project, such as a Node engines field or version file, and the package manager implied by the committed lockfile. Do not mix lockfiles casually. A lockfile records dependency resolution, but it does not replace reading install errors or security notices.
Install dependencies reproducibly
For a Node project with a compatible package-lock.json, npm ci performs a clean lockfile-based install and fails when package metadata and the lock disagree. Use the project's documented command rather than assuming every repository uses npm. Keep generated dependency directories out of version control.
Document configuration without secrets
List required variable names in .env.example with safe placeholders or non-sensitive local defaults. Put .env in .gitignore and supply real values through an approved local or deployment mechanism. Never commit API keys, passwords, private certificates, session cookies or production connection strings.
Make services and ports explicit
Document every local service, port, database name and startup order. Prefer loopback addresses for practice services. If a port is occupied, identify the process and decide whether to stop it or choose a documented alternative; do not terminate unrelated processes blindly.
Prepare data safely
Use migrations, a schema script or a clearly versioned fixture to build local data. Synthetic or sanitized records should be labelled. Never copy production personal data into a laptop just to make a tutorial work. Record reset steps so repeated practice starts from a known state.
Run checks before feature work
Execute the project's formatter, linter, unit tests and a small smoke test before changing code. A failing baseline belongs in the setup notes; otherwise a later developer may attribute it to their change. The example checker below verifies only a few repository conventions and is not a substitute for project tests.
Close the reproducibility loop
Ask another learner or a clean CI environment to follow the written steps. Record the exact failure, command and relevant non-sensitive versions when setup breaks. Remove temporary debugging output, stop local services and keep the guide updated when runtime, lockfile, variables or migrations change.
Small Node workspace convention checker
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import { pathToFileURL } from "node:url";
async function text(path, issues, label) {
try {
return await readFile(path, "utf8");
} catch {
issues.push(`missing ${label}`);
return "";
}
}
export async function checkWorkspace(root) {
const issues = [];
const packageText = await text(resolve(root, "package.json"), issues, "package.json");
const ignoreText = await text(resolve(root, ".gitignore"), issues, ".gitignore");
const envExample = await text(resolve(root, ".env.example"), issues, ".env.example");
if (packageText) {
try {
const pkg = JSON.parse(packageText);
if (typeof pkg.scripts?.test !== "string" || !pkg.scripts.test.trim()) {
issues.push("package.json needs a test script");
}
} catch {
issues.push("package.json is not valid JSON");
}
}
const ignoreLines = ignoreText.split(/\r?\n/).map(line => line.trim());
if (ignoreText && !ignoreLines.includes(".env")) {
issues.push(".gitignore must include .env");
}
const envKeys = envExample
.split(/\r?\n/)
.map(line => line.trim())
.filter(line => line && !line.startsWith("#"))
.map(line => line.split("=", 1)[0]);
if (envExample && envKeys.some(key => !/^[A-Z][A-Z0-9_]*$/.test(key))) {
issues.push(".env.example contains an invalid key");
}
return issues;
}
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
const issues = await checkWorkspace(process.cwd());
if (issues.length) {
console.error(issues.join("\n"));
process.exitCode = 1;
} else {
console.log("workspace checks passed");
}
}
Expected result
A workspace with valid package.json test script, .gitignore containing .env, and valid uppercase keys in .env.example prints: workspace checks passed. Missing or invalid files produce specific lines and a non-zero exit code.
Further reading
Check your progress
Keep your working files, test cases and notes together. Explain one design decision, demonstrate a failure case and list what you would improve next. These concrete outputs make it easier to identify the next skill to practice.