Skip to content

leonardoazeredo/searchland-take-home

Repository files navigation

Searchland Feedback Tech Test

A full-stack TypeScript application with a React frontend and Express backend using tRPC for type-safe API communication. Implements a user feedback system with CRUD operations.

Quick Start

# 1. Install dependencies
pnpm install

# 2. Start the database (Docker required)
pnpm db:up

# 3. Push database schema
pnpm db:push

# 4. Run development servers
pnpm dev

Open http://localhost:5173 to view the app.


Prerequisites

Tool Version Why
Node.js v25+ Runtime
pnpm v10+ Package manager
Docker Latest PostgreSQL database

Verify Installation

node --version    # v25 or higher
pnpm --version    # v10 or higher
docker --version  # Any recent version

Setup Instructions

1. Clone & Install

git clone [email protected]:leonardoazeredo/searchland-take-home.git
cd searchland-take-home
pnpm install

2. Environment Variables

Copy the example environment file:

cp .env.example .env

Default values work out of the box:

  • Database: postgresql://postgres:password@localhost:5433/searchland
  • Server Port: 3001
  • Client: Auto-detects server URL

3. Start Database

pnpm db:up

This starts PostgreSQL on port 5433 using Docker.

Verify it's running:

docker ps  # Should show postgres container

4. Initialize Database

pnpm db:push

This creates the feedback table with schema:

  • id (serial, primary key)
  • name (varchar 255)
  • email (varchar 255)
  • message (text)
  • createdAt (timestamp)

5. Start Development Servers

pnpm dev

This runs both:


Available Commands

Command Description
pnpm dev Start both client & server in dev mode
pnpm build Build both apps for production
pnpm lint Check code style with Biome
pnpm lint:fix Auto-fix linting issues
pnpm typecheck Type-check all packages
pnpm db:up Start PostgreSQL container
pnpm db:down Stop PostgreSQL container
pnpm db:push Push schema changes to database

Project Structure

searchland-take-home/
├── apps/
│   ├── client/          # React 19 + Vite + TailwindCSS 4
│   │   ├── src/
│   │   │   ├── components/
│   │   │   ├── pages/
│   │   │   ├── trpc.ts
│   │   │   └── main.tsx
│   │   └── package.json
│   │
│   └── server/          # Express 5 + tRPC
│       ├── src/
│       │   ├── index.ts
│       │   └── trpc.ts
│       └── package.json
│
├── packages/
│   ├── api/             # Shared tRPC routers
│   │   ├── src/
│   │   │   ├── router/
│   │   │   └── trpc.ts
│   │   └── package.json
│   │
│   └── db/              # Database schema & client
│       ├── src/
│       │   ├── schema.ts
│       │   └── index.ts
│       └── package.json
│
├── docker-compose.yml   # PostgreSQL configuration
├── .env                 # Environment variables
├── .env.example         # Example environment file
├── biome.json           # Linting/formatting config
├── package.json         # Root package.json
└── tsconfig.base.json   # Shared TypeScript config

Tech Stack

Frontend

  • React 19 - UI framework
  • Vite - Build tool & dev server
  • TailwindCSS 4 - Styling
  • tRPC - Type-safe API client
  • TanStack React Query - Data fetching
  • Zod - Form validation
  • Lucide React - Icons

Backend

  • Express 5 - Web server
  • tRPC - Type-safe API
  • Drizzle ORM - Database ORM
  • PostgreSQL 17 - Database
  • Zod - Input validation

Development

  • TypeScript - Type safety
  • Biome - Linting & formatting
  • Docker - Database container

API Endpoints

tRPC Procedures

Procedure Method Description
feedback.getAll Query Get all feedback (paginated, 20 per page)
feedback.getById Query Get single feedback by ID
feedback.create Mutation Create new feedback
feedback.update Mutation Update existing feedback
feedback.delete Mutation Delete feedback by ID

REST Endpoints

Endpoint Method Description
/health GET Health check with DB status

Database Schema

feedback {
  id          Int      @id @default(autoincrement())
  name        String   @db.VarChar(255)
  email       String   @db.VarChar(255)
  message     String
  createdAt   DateTime @default(now())
  deletedAt   DateTime?  // Soft delete (nullable)

  @@index([createdAt])
  @@index([email])
  @@index([deletedAt])
}

Running Linter

pnpm lint      # Check for issues
pnpm lint:fix  # Auto-fix issues

Deployment

Build for Production

pnpm build

Output:

  • Client: apps/client/dist/
  • Server: apps/server/dist/

Technical Decisions & Trade-offs

To ensure this project reflects how I would architect a production-ready application for a scaling team, I made a few intentional additions to the required stack:

  • Vite (vs. Create React App / Next.js):
    • Why: Unmatched developer experience (DX) and HMR speed. Since the prompt asked for a React SPA (and didn't explicitly require SSR/SEO), Vite is the most pragmatic choice for rapid iteration during the live coding stage.
    • Trade-off: Lacks built-in file-system routing and SSR compared to Next.js, but React Router v7 handles the client-side routing perfectly for this scope.
  • Zod:
    • Why: Essential for tRPC. It provides runtime validation that perfectly infers to TypeScript types, ensuring end-to-end type safety across the network boundary.
    • Trade-off: Adds a small amount to the client bundle size, but the guarantee of data integrity on the backend is worth the cost.
  • Biome (vs. ESLint + Prettier):
    • Why: A single, incredibly fast Rust-based toolchain that handles both linting and formatting. It removes the configuration overhead of making ESLint and Prettier play nicely together.
    • Trade-off: Newer ecosystem with fewer community plugins than ESLint, but covers 99% of standard TypeScript/React needs out of the box.
  • pnpm Workspaces (Monorepo):
    • Why: Separating the client, server, api (tRPC routers), and db into distinct packages enforces strict architectural boundaries. It prevents the frontend from accidentally importing server-only code (like Node fs or DB credentials).

License

AGPL-3.0

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors