This walkthrough covers Build a React CRM with MySQL. An authenticated React and Node application with organization-scoped access. Work with fictional contacts and a dedicated development database.
The video is an illustrated code walkthrough with AI narration. Code blocks are integration excerpts, not a complete tested application. Helper names such as stores, middleware and clients describe responsibilities you must implement in your existing project. No live server, payment provider or device execution is claimed.
Before you begin
Use a separate development environment, fictional data and test credentials. Record dependency versions and follow the official references below for your installed versions. Keep a working baseline and add one workflow at a time.
1. Model the relationships
Start with separate records for companies, people, notes, and opportunities. A contact can refer to a company without duplicating the company's details in every row. Include the organization boundary in the model from the start. Keep notes linked to their author. Treat monetary values and currencies explicitly instead of relying on floating-point display values as your financial record.
companies: id, tenant_id, name
contacts: id, tenant_id, company_id, name, email
notes: id, tenant_id, contact_id, author_id, body
deals: id, tenant_id, company_id, stage, amount_minor2. Create a focused contact endpoint
Define a small set of routes around the first workflow: find a person, view their details, edit them, and add a note. Keep organization identity on the server. Validate that referenced company and contact records belong to that same organization. Use predictable error responses so the interface can distinguish invalid input from an unavailable record or temporary service failure.
GET /api/contacts?q=alex&after=120
POST /api/contacts
PATCH /api/contacts/:id
POST /api/contacts/:id/notes3. Filter inside the tenant
Scope every search to the signed-in organization before applying filters. This query uses a stable identifier cursor for a simple ordered list. Bind the tenant, cursor, and search value separately from the SQL. Define how wildcard characters behave. Add an index that supports your common filter and ordering pattern, and measure real queries before introducing more elaborate search infrastructure.
SELECT id,name,email FROM contacts
WHERE tenant_id=? AND id>? AND name LIKE ?
ORDER BY id LIMIT 25;4. Build a master-detail interface
A searchable list and a detail panel provide a practical first CRM layout. Separate selection from form editing so changing the selected contact doesn't silently discard unsaved changes. Give the detail view loading, missing, and failure states. Use semantic labels and keyboard-accessible controls, and make the mobile layout work as a sequence of screens instead of squeezing two unreadable panels together.
<ContactSearch query={query} onChange={setQuery} />
<ContactList contacts={contacts} onSelect={setSelected} />
<ContactDetails contactId={selected} />5. Prevent lost edits
Two staff members may edit the same record. Add a version field and require the update to match the version the editor originally loaded. If it no longer matches, return a conflict and let the user refresh or reconcile the changes. Don't silently overwrite newer information. The database query must include the organization boundary as well as the identifier and version.
UPDATE contacts SET name=?, email=?, version=version+1
WHERE id=? AND tenant_id=? AND version=?;
-- No matching version: return a conflict response.6. Control sales-stage transitions
Represent pipeline stages with a controlled set of values. If the business has transition rules, enforce them on the server and record the change with the actor and timestamp. Keep revenue calculations separate from optimistic interface animations. For a first version, a reliable table with filters can be more useful than a drag-and-drop board with ambiguous save behaviour.
const stages = ["lead", "qualified", "proposal", "won", "lost"];
if (!stages.includes(nextStage)) return res.sendStatus(400);
await changeStageAndAudit({dealId, tenantId, actorId, nextStage});7. Design export and deletion policies
Reports and exports need the same access boundaries as normal screens. Limit large exports, protect spreadsheet consumers from formula-like fields where applicable, and record sensitive export actions. Define how deletion affects linked notes and deals before implementing it. Use fictional data in demonstrations and tests. A CRM's data lifecycle matters as much as its contact form.
Export: authorize -> scope -> limit -> audit
Delete: confirm -> apply retention policy -> audit
Never export another tenant through an unscoped report.Integration checks
Test cross-organization identifiers, duplicate input, concurrent edits, empty search, pagination, stage validation and permission checks on exports.
These are verification instructions, not reported test results. The excerpts have not been executed as a complete integrated application. Review authentication, database schema, dependency versions and operational settings before deployment.
Official references
Explore CodeKits development services.

