A small full-stack application that integrates with a Salesforce Developer Edition to list and create Account records.
The application allows users to:
- View Salesforce Account records in a paginated table with server-side pagination (15 per page).
- Create new Account records via a Salesforce-console-style form with 30+ fields, saved directly to Salesforce.
- View account details in a modal with all fields grouped by section.
- Search parent accounts with a debounced, server-side paginated dropdown.
- Authenticate with Salesforce via OAuth 2.0 (authorization-code flow with PKCE).
All Salesforce communication happens server-side. The React frontend never touches Salesforce credentials, access tokens, or the Salesforce API directly.
React + Vite → NestJS (Node.js) → Salesforce REST API
- Frontend (React + Vite + Tailwind CSS): renders the UI and calls the backend API over HTTP. Uses axios for API calls with automatic redirect to OAuth on 401. Toast notifications via Sonner.
- Backend (NestJS + TypeScript): handles OAuth, token management, and all Salesforce REST API communication. Returns application-level Account objects to the frontend.
- Salesforce: the external system of record. The backend talks to it via the REST API using OAuth access tokens.
noca-interview-app/
backend/
src/
accounts/ # Accounts module
accounts.controller.ts # GET/POST /api/accounts (pagination, search)
accounts.service.ts # Business logic, Salesforce response mapping
accounts.module.ts # Module wiring
dto/ # CreateAccountDto with class-validator decorators
enums.ts # Picklist enum values (Rating, Type, Industry, etc.)
types.ts # Account, PaginatedAccounts, Salesforce types
salesforce/ # Salesforce integration module
salesforce.service.ts # OAuth, token management, SOQL/SOSL, REST API calls
auth.controller.ts # OAuth endpoints (/auth/salesforce/*)
salesforce.module.ts # Module wiring
types.ts # Salesforce response types
app.module.ts # Root NestJS module
main.ts # Bootstrap, CORS, ValidationPipe, .env loading
frontend/
src/
components/ # AccountTable, AccountForm, AccountDetail, Modal, ComboSearch, ParentAccountSearch
constants/ # picklists.ts (enum values), styles.ts (shared Tailwind classes)
services/ # API client (axios)
types/ # Account interface
App.tsx # Root component (state management, data flow)
main.tsx # React entry point
tailwind.config.js # Zinc-based neutral palette with blue accents
docs/ # Assignment rules and implementation plan
.env.example # Template for environment variables
- Node.js >= 20
- Yarn 4.x (Berry)
- A Salesforce Developer Edition org with an External Client App configured for OAuth
This project uses an existing Salesforce External Client App. You need to configure it with the following:
- Callback URL:
http://localhost:3000/auth/salesforce/callback - OAuth Scopes:
apiandrefresh_token - PKCE: enabled (the app sends
code_challengewithS256method)
From the External Client App, obtain:
- Consumer Key (Client ID)
- Consumer Secret (Client Secret)
Copy .env.example to .env at the repository root and fill in the values:
cp .env.example .env| Variable | Description | Example |
|---|---|---|
SALESFORCE_CLIENT_ID |
OAuth Consumer Key from the External Client App | (from Salesforce) |
SALESFORCE_CLIENT_SECRET |
OAuth Consumer Secret (server-side only) | (from Salesforce) |
SALESFORCE_CALLBACK_URL |
OAuth redirect URI (must match Salesforce config) | http://localhost:3000/auth/salesforce/callback |
SALESFORCE_LOGIN_URL |
Salesforce login/token endpoint base URL | https://login.salesforce.com |
PORT |
Backend port | 3000 |
FRONTEND_ORIGIN |
Frontend origin for CORS | http://localhost:5173 |
Never commit
.env. It is gitignored. The.env.examplefile documents variable names without exposing values.
# Install all workspace dependencies
yarn installyarn devThis starts the NestJS backend (port 3000) and Vite frontend (port 5173) in parallel.
yarn dev:backend # NestJS on http://localhost:3000
yarn dev:frontend # Vite on http://localhost:5173- Open
http://localhost:5173in your browser. - The app will detect that you're not authenticated and automatically redirect to
/auth/salesforce. - Log in to Salesforce and authorize the app.
- After authorization, you'll be redirected back to the frontend.
- The Accounts table will load with your Salesforce data.
On subsequent visits, the backend loads the persisted token from backend/.salesforce-token.json
(gitignored), so re-authentication is only needed when the token expires or is revoked.
- Server-side pagination (15 accounts per page) via Salesforce SOQL
LIMIT/OFFSET. - Columns: Name, Site, Phone, Type, Owner Alias.
- Click a row to view full account details in a modal.
- Loading, error, and empty states.
- Refresh button to reload the current page.
- Salesforce-console-style form with 30+ fields organized into sections:
- Account Information: Name (required), Rating, Parent Account, Phone, Fax, Account Number, Website, Site, Ticker Symbol, Type, Ownership, Industry, Number of Employees, Annual Revenue, SIC Code.
- Address Information: Billing and Shipping address fields (Country, Street, City, State, Postal Code).
- Additional Information: Customer Priority, SLA, SLA Serial Number, SLA Expiration Date, Number of Locations, Upsell Opportunity, Active.
- Description: Free-text description.
- Picklist fields validated against Salesforce enum values.
- Country and US State selectors with searchable dropdowns.
- Parent Account lookup with debounced server-side search and lazy-loaded pagination.
- Account Owner displayed from the current Salesforce user.
- Success/error feedback via toast notifications (Sonner).
- Modal closes on successful creation; stays open on error so the user can correct and retry.
- Read-only modal showing all account fields grouped by section.
- Displays parent account name and owner alias (fetched via Salesforce relationship fields).
GET /api/accounts # Paginated account list (limit, offset)
GET /api/accounts?q=term # Search accounts by name (server-side paginated)
POST /api/accounts # Create a new account
GET /auth/salesforce # Redirect to Salesforce authorization page
GET /auth/salesforce/callback # OAuth callback (exchanges code for tokens)
GET /auth/salesforce/me # Current Salesforce user info
yarn testRuns the backend test suite (Jest + ts-jest + supertest) with mocked Salesforce communication. Tests cover:
- Successful Account retrieval (paginated)
- Successful Account creation
- Salesforce API failure handling
- Invalid Account creation requests (validation: empty, missing, too long, wrong type, extra fields, invalid enum values)
- Pagination parameter clamping and NaN handling
- Search with query parameters
25 tests across 2 test suites. No real Salesforce connection is required for automated tests.
- Start the backend and frontend (
yarn dev). - Complete the OAuth flow (see above).
- Verify existing Accounts appear in the table.
- Navigate through pages using the pagination controls.
- Click an account row to view its details in the modal.
- Create a new Account via the form and verify it appears in the table.
- Verify the new Account exists in Salesforce directly.
yarn lint # ESLint across both workspaces
yarn format:check # Prettier formatting check
yarn typecheck # TypeScript type-checking across both workspaces
yarn build # Build all workspacesSalesforce credentials (Consumer Secret, access tokens, refresh tokens) must never be exposed to the browser. A server-side backend keeps all sensitive values on the server and exposes only application-level data to the frontend.
If Salesforce credentials were in the React bundle, anyone could extract them from the browser and impersonate the application. The frontend only talks to the NestJS backend, which acts as a secure proxy.
NestJS provides a structured, opinionated framework with built-in support for dependency injection, modules, validation pipes, and exception handling. It's well-suited for a small Node.js backend that needs clean separation between HTTP controllers, business logic, and external API integration.
Salesforce is the system of record. The application doesn't need its own database — it reads and writes Accounts directly to Salesforce via the REST API. OAuth tokens are persisted to a local JSON file (gitignored) for convenience, not a database.
The backend uses Salesforce SOQL LIMIT/OFFSET to fetch only the requested page of records.
This avoids loading the entire Account collection into memory or the browser, and scales better
as the number of accounts grows.
- OAuth 2.0 with PKCE: Authorization-code flow with S256 code challenge. Cryptographically
secure state parameter (
randomBytes(32)). - Token storage: Access and refresh tokens stored server-side only, persisted to a gitignored JSON file. Never exposed to the frontend.
- Least-privilege scopes: Only
apiandrefresh_tokenscopes requested. Thefullscope is intentionally not requested. - Input validation:
ValidationPipewithwhitelistandforbidNonWhitelistedrejects unknown fields. All DTO fields validated withclass-validatordecorators (@IsString,@IsEnum,@IsNumber,@MaxLength,@IsDateString). - Pagination clamping:
limitclamped to[1, 200],offsetclamped to[0, MAX_SAFE_INTEGER]. Non-numeric values fall back to defaults. - SOSL injection prevention: Search terms escape backslashes and single quotes before interpolation into SOSL queries.
- Error handling: Salesforce errors are wrapped into generic NestJS exceptions. No Salesforce response bodies, status codes, or token values are leaked to the client.
- CORS: Restricted to
FRONTEND_ORIGINenvironment variable. - No XSS vectors: No
dangerouslySetInnerHTMLorinnerHTMLusage. React auto-escapes all rendered content.
- Backend: Node.js, TypeScript (strict), NestJS 10, axios, class-validator, dotenv
- Frontend: React 18, TypeScript (strict), Vite, Tailwind CSS (Zinc palette with blue accents), axios, Sonner (toasts), lucide-react (icons), country-list, usa-states
- Testing: Jest, ts-jest, supertest, @nestjs/testing
- Tooling: Yarn 4 (Berry workspaces), ESLint 9 (flat config), Prettier
- Salesforce: REST API v60.0, OAuth 2.0 authorization-code flow with PKCE, SOQL + SOSL