Skip to content

Repository files navigation

Noca Interview App

A small full-stack application that integrates with a Salesforce Developer Edition to list and create Account records.

Overview

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.

Architecture Overview

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

Prerequisites

  • Node.js >= 20
  • Yarn 4.x (Berry)
  • A Salesforce Developer Edition org with an External Client App configured for OAuth

Salesforce Setup

This project uses an existing Salesforce External Client App. You need to configure it with the following:

  1. Callback URL: http://localhost:3000/auth/salesforce/callback
  2. OAuth Scopes: api and refresh_token
  3. PKCE: enabled (the app sends code_challenge with S256 method)

From the External Client App, obtain:

  • Consumer Key (Client ID)
  • Consumer Secret (Client Secret)

Environment Variables

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.example file documents variable names without exposing values.

Installation

# Install all workspace dependencies
yarn install

Running

Start both backend and frontend

yarn dev

This starts the NestJS backend (port 3000) and Vite frontend (port 5173) in parallel.

Start individually

yarn dev:backend    # NestJS on http://localhost:3000
yarn dev:frontend   # Vite on http://localhost:5173

First-time authentication

  1. Open http://localhost:5173 in your browser.
  2. The app will detect that you're not authenticated and automatically redirect to /auth/salesforce.
  3. Log in to Salesforce and authorize the app.
  4. After authorization, you'll be redirected back to the frontend.
  5. 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.

Features

Account List

  • 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.

Account Creation

  • 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.

Account Detail

  • Read-only modal showing all account fields grouped by section.
  • Displays parent account name and owner alias (fetched via Salesforce relationship fields).

API Endpoints

Account endpoints

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

OAuth endpoints

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

Testing

Automated tests

yarn test

Runs 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.

Manual end-to-end testing

  1. Start the backend and frontend (yarn dev).
  2. Complete the OAuth flow (see above).
  3. Verify existing Accounts appear in the table.
  4. Navigate through pages using the pagination controls.
  5. Click an account row to view its details in the modal.
  6. Create a new Account via the form and verify it appears in the table.
  7. Verify the new Account exists in Salesforce directly.

Code quality checks

yarn lint            # ESLint across both workspaces
yarn format:check    # Prettier formatting check
yarn typecheck       # TypeScript type-checking across both workspaces
yarn build           # Build all workspaces

Architecture Decisions

Why Salesforce access is server-side

Salesforce 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.

Why React does not contain Salesforce credentials

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.

Why NestJS is used

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.

Why no database is required

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.

Why server-side pagination

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.

Security

  • 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 api and refresh_token scopes requested. The full scope is intentionally not requested.
  • Input validation: ValidationPipe with whitelist and forbidNonWhitelisted rejects unknown fields. All DTO fields validated with class-validator decorators (@IsString, @IsEnum, @IsNumber, @MaxLength, @IsDateString).
  • Pagination clamping: limit clamped to [1, 200], offset clamped 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_ORIGIN environment variable.
  • No XSS vectors: No dangerouslySetInnerHTML or innerHTML usage. React auto-escapes all rendered content.

Tech Stack

  • 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

About

Salesforce Accounts manager. Fullstack app (NestJS + React/Vite) — an interview assignment, mostly AI-generated

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages