DevAssist AI is a full-stack AI developer toolkit built with React, Express, TypeScript, and OpenAI. It showcases real-world AI patterns including structured prompting, API orchestration, and a Retrieval-Augmented Generation (RAG) pipeline for grounded responses.
The app is designed to work for two audiences:
- Non-technical users can open the web app, choose a tool, and use AI through a polished interface with mock data or real API calls.
- Technical users can study the codebase as a working example of OpenAI integration, API validation, rate limiting, Swagger docs, and a simple RAG pipeline.
These demos showcase real AI workflows including summarization, code explanation, and a RAG-powered portfolio assistant.
DevAssist AI demonstrates a full-stack AI architecture where a React frontend communicates with an Express API that manages OpenAI interactions, validation, rate limiting, and a Retrieval-Augmented Generation (RAG) pipeline for grounded responses.
- Smart Summarizer turns long text into a summary, key takeaways, and action items.
- Code Explainer explains pasted code in plain English with step-by-step notes, important concepts, and improvement ideas.
- Portfolio Chat answers questions from local portfolio Markdown documents using RAG.
- Mock/API toggle lets the frontend switch between local mock responses and real API calls.
- Light/dark theme gives users a persistent theme choice.
- Swagger docs document and test the Express API in the browser.
DevAssist AI has two apps in one npm workspace:
apps/web: React frontend for the AI tools and user experience.apps/api: Express backend that owns OpenAI calls, API validation, rate limiting, Swagger docs, and portfolio retrieval.
The frontend never calls OpenAI directly. It sends requests to the API, and the API handles secrets, prompt building, embeddings, retrieval, and model calls.
React app
-> Express API
-> OpenAI chat and embeddings APIs
-> structured response back to React
flowchart TD
%% Layers
subgraph USER["👤 User Experience Layer"]
User["User"]
Web["React Web App<br/>Vite • TypeScript • TanStack Router<br/>AI Tools • Theme Toggle • Mock/API Toggle"]
end
subgraph API["⚙️ Express API Layer"]
Express["Express API<br/>Validation • Rate Limiting • Swagger Docs"]
Ask["POST /ai/ask<br/>Summarizer • Code Explainer"]
Chat["POST /ai/portfolio-chat<br/>RAG Portfolio Chat"]
end
subgraph RAG["🧠 RAG Pipeline"]
Markdown["Portfolio Markdown Files<br/>data/portfolio"]
Ingest["Ingestion Script<br/>Chunk Documents"]
Embeddings["OpenAI Embeddings<br/>text-embedding-3-small"]
Index["Local Vector Index<br/>data/portfolio-index.json"]
Retrieve["Retrieve Top Chunks<br/>Cosine Similarity"]
end
subgraph AI["🤖 OpenAI Layer"]
ChatModel["Chat Model<br/>Structured AI Responses"]
end
User --> Web
Web -->|Mock Mode| Mock["Local Mock Responses"]
Web -->|API Mode| Express
Express --> Ask
Express --> Chat
Ask --> ChatModel
Markdown --> Ingest
Ingest --> Embeddings
Embeddings --> Index
Chat --> Retrieve
Retrieve --> Index
Retrieve --> ChatModel
ChatModel --> Express
Express --> Web
Web --> User
%% Styling
classDef user fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0f172a;
classDef api fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#0f172a;
classDef rag fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#0f172a;
classDef ai fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#0f172a;
classDef mock fill:#f3f4f6,stroke:#9ca3af,stroke-width:1px,color:#374151;
class User,Web user;
class Express,Ask,Chat api;
class Markdown,Ingest,Embeddings,Index,Retrieve rag;
class ChatModel ai;
class Mock mock;
This architecture keeps OpenAI interactions on the server, enabling secure API usage, controlled prompting, and a reusable RAG pipeline that can scale beyond a single application.
sequenceDiagram
participant User
participant UI as React App
participant API as Express API
participant RAG as Retrieval Layer
participant OpenAI
User->>UI: Enter prompt / question
alt Mock Mode
UI-->>User: Return mock response
else API Mode
UI->>API: POST /ai/ask or /ai/portfolio-chat
API->>API: Validate request\nRate limit\nBuild prompt
alt Portfolio Chat (RAG)
API->>RAG: Retrieve relevant chunks
RAG->>RAG: Embed question
RAG->>RAG: Rank chunks (cosine similarity)
RAG-->>API: Top matching context
end
API->>OpenAI: Send prompt + context
OpenAI-->>API: Structured response
API-->>UI: JSON response
UI-->>User: Render answer + sources
end
This flow shows how DevAssist AI handles both direct AI prompts and RAG-based queries, keeping retrieval, validation, and OpenAI interactions securely within the API layer.
- React 19
- TypeScript
- Vite
- TanStack Router
- Tailwind CSS
- shadcn/ui-style components
- Radix UI primitives
- lucide-react icons
- next-themes
- Vitest and Testing Library
- Node.js
- Express 5
- TypeScript
- OpenAI SDK
- dotenv-style environment loading through Node
--env-file - CORS
- Swagger JSDoc
- Swagger UI Express
- tsx for local TypeScript development
- npm workspaces
- Root scripts for running, building, linting, formatting, testing, and portfolio ingestion
- Shared root
package-lock.json
Install dependencies from the repo root:
npm installCreate local environment files:
cp apps/api/.env.example apps/api/.env
cp apps/web/.env.example apps/web/.envSet your OpenAI API key in apps/api/.env:
PORT=5000
OPENAI_API_KEY=your_api_key_here
CORS_ORIGIN=http://localhost:3000
AI_MAX_PROMPT_CHARACTERS=12000
AI_RATE_LIMIT_WINDOW_MS=60000
AI_RATE_LIMIT_MAX_REQUESTS=10
PORTFOLIO_EMBEDDING_MODEL=text-embedding-3-small
PORTFOLIO_CHAT_MODEL=gpt-4o-miniSet the frontend dev configuration in apps/web/.env:
VITE_APP_PORT=3000
VITE_API_URL=http://localhost:5000Run both apps:
npm run devDefault local URLs:
- Web app:
http://localhost:3000 - API server:
http://localhost:5000 - Swagger docs:
http://localhost:5000/swagger
The navigation includes a global Mock data / API calls toggle.
- Mock data uses local mock functions and does not require the API server or an OpenAI key.
- API calls sends requests from the frontend to the Express API.
- The selected mode is saved in
localStorage. - The toggle applies to Summarizer, Code Explainer, and Portfolio Chat.
For the easiest local workflow, run both apps:
npm run dev- Open the web app.
- Go to
Summarizer. - Paste meeting notes, article text, transcript content, or other long-form text.
- Choose a tone:
Professional,Concise, orFriendly. - Click
Summarize.
In API mode, the frontend sends the text to POST /ai/ask and asks the model to return structured content for the UI.
- Go to
Code Explainer. - Paste code into the form.
- Select the language.
- Run the explanation.
In API mode, the app asks for an overview, step-by-step explanation, important concepts, and possible improvements.
- Go to
Portfolio Chat. - Ask a question about projects, experience, architecture, frontend work, backend APIs, or AI work.
- Review the answer and source badges.
- Continue the conversation or reset the chat.
Before using Portfolio Chat in API mode, generate the retrieval index:
npm run ingest:portfolioThis command reads Markdown files under data/portfolio, chunks them, creates embeddings, and writes data/portfolio-index.json.
The API exposes two AI routes:
POST /ai/ask
POST /ai/portfolio-chat
Example request:
{
"prompt": "Explain React hooks in one paragraph."
}Example response:
{
"reply": "React hooks let function components manage state and side effects..."
}Example request:
{
"message": "What backend API experience does Steven have?",
"history": []
}Example response:
{
"reply": "Steven has experience building Express APIs...",
"sources": [
{
"id": "projects-api-1",
"label": "API Projects"
}
]
}The API validates request shape, limits prompt size, rate-limits requests per client IP, and keeps the OpenAI API key on the server.
Retrieval-Augmented Generation, usually shortened to RAG, is a way to make AI answers grounded in your own content. Instead of asking a model to answer from memory, the app retrieves relevant documents first, gives those documents to the model as context, and asks the model to answer from that context.
In DevAssist AI, RAG powers Portfolio Chat.
The flow has five main parts:
- Store portfolio source content as Markdown files.
- Split those files into smaller chunks.
- Generate embeddings for each chunk with OpenAI.
- Retrieve the most relevant chunks for a user's question.
- Send the retrieved context to an OpenAI chat model and render the answer in React.
Markdown files
-> chunk documents
-> create embeddings
-> save vector index
-> embed user question
-> rank chunks by similarity
-> build prompt with retrieved context
-> generate answer
-> show answer and source badges in React
This same pattern can be used for documentation assistants, internal knowledge bases, product support bots, onboarding tools, and other apps where answers need to be grounded in private or project-specific data.
The RAG code is split between the API and the React frontend:
apps/api/src/lib/portfolio/
build-portfolio-chat-messages.ts
chunk-documents.ts
embeddings.ts
index-store.ts
load-documents.ts
paths.ts
retrieve-chunks.ts
types.ts
apps/api/src/routes/
portfolio-chat.ts
apps/api/src/scripts/
ingest-portfolio.ts
apps/web/src/features/portfolio-chat/
api/portfolio-chat-api.ts
components/PortfolioChatForm.tsx
components/PortfolioChatMessages.tsx
pages/PortfolioChatPage.tsx
types.ts
data/portfolio/
notes/
projects/
data/portfolio-index.json
The application keeps its knowledge base as Markdown files under data/portfolio. Each file becomes a portfolio document with an ID, title, path, and text.
This keeps the first version simple: no database is required to prove the architecture, and the source material stays readable and version controlled.
Large documents are not ideal retrieval units. If a full Markdown file contains several topics, retrieving the whole file can add noise to the model prompt.
The API splits documents into smaller chunks, with a small overlap between chunks. The overlap helps preserve meaning when a useful sentence sits near a chunk boundary.
An embedding turns text into a vector, which is a list of numbers that represents semantic meaning. Text with similar meaning should have vectors that are close together.
DevAssist AI uses text-embedding-3-small by default:
const embeddingModel =
process.env.PORTFOLIO_EMBEDDING_MODEL ?? 'text-embedding-3-small'The API has two embedding helpers:
embedTextsembeds document chunks during ingestion.embedTextembeds one user question at request time.
The ingestion script turns Markdown documents into a local JSON index:
npm run ingest:portfolioThe generated index is saved to:
data/portfolio-index.json
For a production system, this JSON file could be replaced by a vector database such as PostgreSQL with pgvector, Pinecone, Qdrant, Weaviate, or another vector store. For a focused proof of concept, JSON keeps the retrieval logic transparent.
When the user asks a question, the API embeds that question and compares it with the stored chunk embeddings.
The retrieval code uses cosine similarity, then keeps the top matches:
export async function retrievePortfolioChunks(
question: string,
limit = 4
): Promise<RetrievedPortfolioChunk[]> {
const [chunks, questionEmbedding] = await Promise.all([
loadPortfolioIndex(),
embedText(question),
])
return rankChunks(chunks, questionEmbedding, limit)
}The default limit of 4 gives the model useful context without stuffing the prompt with too much unrelated material.
Retrieval alone is not enough. The model also needs clear instructions about how to use the retrieved content.
The API builds a prompt that includes:
- A system instruction to answer only from retrieved portfolio context.
- Recent chat history, limited to the last six messages.
- Retrieved chunks with source metadata.
- The user's current message.
That source metadata is returned to the frontend and displayed as source badges.
Portfolio Chat renders source badges underneath assistant messages. These small badges are an important trust signal because they show which documents shaped the answer.
{!isUser && message.sources?.length ? (
<div className="mt-4 flex flex-wrap gap-2">
{message.sources.map((source) => (
<Badge key={source.id} variant="secondary">
{source.label}
</Badge>
))}
</div>
) : null}This implementation is intentionally simple, which makes it useful as a learning example.
It keeps the OpenAI key on the server, separates ingestion from runtime chat, returns source metadata to the UI, and instructs the model to answer only from retrieved context. Those choices are the foundation of a reliable RAG app.
Root scripts:
npm run dev # Run the API and frontend in parallel
npm run dev:web # Run the frontend
npm run dev:api # Run the API
npm run ingest:portfolio # Build the portfolio RAG index
npm run build # Build all workspaces
npm run build:web # Build the frontend
npm run build:api # Build the API
npm run lint # Lint all workspaces
npm run lint:fix # Fix lint issues where possible
npm run format # Format the repo
npm run format:check # Check formatting
npm run test # Run tests
npm run test:web # Run frontend tests
npm run start:api # Build and start the compiled API.
├── apps
│ ├── api
│ │ ├── src
│ │ │ ├── app.ts
│ │ │ ├── server.ts
│ │ │ ├── config
│ │ │ ├── lib
│ │ │ │ └── portfolio
│ │ │ ├── middleware
│ │ │ ├── routes
│ │ │ └── scripts
│ │ └── package.json
│ └── web
│ ├── src
│ │ ├── components
│ │ ├── features
│ │ ├── pages
│ │ ├── routes
│ │ └── main.tsx
│ └── package.json
├── assets
├── data
│ └── portfolio
├── package-lock.json
├── package.json
├── RAG_ARTICLE.md
└── tsconfig.json
Possible next steps:
- Replace the JSON vector index with a real vector database.
- Add tests around chunking, retrieval ranking, and API validation.
- Add score thresholds so weak retrieval results trigger an "I do not know" response.
- Stream assistant responses to React for a faster chat experience.
- Track token usage, latency, and retrieval quality.
- Add document refresh workflows when portfolio content changes.
- Link source badges directly to source documents or public URLs.
- Restrict CORS origins for deployed environments.
This project complements:
- NodeMovieApi — backend API architecture
- DotNetMovieApi — multi-stack API implementation
- Postgres Movie Platform — shared data layer
Together, these projects demonstrate full-stack, multi-layer system design across frontend, API, and data platform architectures.
Steven Wickers Senior / Lead Frontend Engineer React • TypeScript • Node • C# • PostgreSQL • Cloud
OpenAI API, Retrieval-Augmented Generation (RAG), React, TypeScript, Vite, Express API, Full-Stack AI Application, Embeddings, Prompt Engineering, AI Developer Tools
apps/api/distandapps/web/distare generated build outputs.- Do not edit generated files in
dist; update files insrcand rebuild. - The frontend dev server proxies
/airequests to the API server with Vite. - API route docs are generated from JSDoc comments in
apps/api/src/routes.



