# Component Design Standards for Qwik
This document outlines the coding standards for component design in Qwik, aiming to promote maintainability, reusability, and performance. Adhering to these guidelines ensures efficient collaboration and a robust codebase in accordance with the latest Qwik features and best practices.
## 1. Component Architecture
### 1.1. Component Hierarchy and Structure
* **Do This:** Favor a component-based architecture with a clear hierarchy. Break down complex UI into smaller, reusable components.
* **Don't Do This:** Create monolithic components that handle too many responsibilities.
**Why:** A well-defined component hierarchy simplifies maintenance and promotes reusability. Breaking down complex UIs enables focus on specific features and improve readability.
It also encourages efficient code splitting and lazy loading in Qwik.
**Example:**
"""typescript
// Good: Separated concerns
// card.tsx
import { component$, $, useContext } from '@builder.io/qwik';
import { ThemeContext } from './theme-context';
export const Card = component$(() => {
const theme = useContext(ThemeContext);
return (
);
});
// card-header.tsx
import { component$ } from '@builder.io/qwik';
export const CardHeader = component$(() => {
return (
Card Title
);
});
// card-body.tsx
import { component$ } from '@builder.io/qwik';
export const CardBody = component$(() => {
return (
<p>Card content goes here.</p>
);
});
// theme-context.tsx
import { createContextId } from '@builder.io/qwik';
export const ThemeContext = createContextId<'light' | 'dark'>('theme');
"""
"""typescript
// Bad: Monolithic component
import { component$ } from '@builder.io/qwik';
export const MonolithicCard = component$(() => {
return (
Card Title
<p>Card content goes here.</p>
);
});
"""
### 1.2. Smart vs. Dumb Components (Presentational vs. Container Components)
* **Do This:** Distinguish between smart (container) and dumb (presentational) components. Smart components handle data fetching, state management, and business logic. Dumb components primarily focus on rendering UI based on props.
* **Don't Do This:** Mix data handling and UI rendering logic within a single component.
**Why:** This separation enhances reusability, testability, and maintainability. Presentational components become easily adaptable and restylable for different contexts, and container components facilitate easier state management and testing.
**Example:**
"""typescript
// Smart Component (Container) - data fetching
import { component$, useStore, useTask$ } from '@builder.io/qwik';
import { PresentationalList } from './presentational-list';
export const SmartList = component$(() => {
const state = useStore({ items: [] });
useTask$(async ({ track }) => {
track(() => state); // Re-run when state changes
const fetchData = async () => {
const response = await fetch('/api/items');
state.items = await response.json();
};
fetchData();
});
return ;
})
// Dumb Component (Presentational) - UI rendering
import { component$ } from '@builder.io/qwik';
interface Props {
items: any[];
}
export const PresentationalList = component$(({ items }) => {
return (
{items.map((item) => (
{item.name}
))}
);
});
"""
### 1.3. State Management Strategy
* **Do This:** Favor Qwik's "useStore" and "useSignal" for local component state. "useContext" when sharing state between components that may not neccessarily be parent and child. Consider more advanced state management solutions like Redux or Zustand (though typically not needed) only for complex applications with global state needs.
* **Don't Do This:** Over-engineer state management with complex solutions for simple component state. Mutations should be predictable and avoid hidden side-effects.
**Why:** Using Qwik's built-in state management tools ("useStore", "useSignal", "useContext", and "useTask$") simplifies development and provides performance benefits due to Qwik's resumability feature. External state management libraries introduce additional complexity and may not align with Qwik's core principles.
**Example:**
"""typescript
// Using useStore
import { component$, useStore } from '@builder.io/qwik';
export const Counter = component$(() => {
const state = useStore({ count: 0 });
return (
<p>Count: {state.count}</p>
state.count++}>Increment
);
});
"""
"""typescript
// Using useContext
import { component$, useContext, createContextId } from '@builder.io/qwik';
const MyContext = createContextId<{ value: string }>('my-context');
export const ProviderComponent = component$(() => {
return (
);
});
export const ConsumerComponent = component$(() => {
const contextValue = useContext(MyContext);
return <p>{contextValue.value}</p>;
});
"""
### 1.4. Prop Drilling Avoidance
* **Do This:** Favor "useContext" to share state with deep child components instead of passing props down through multiple levels.
* **Don't Do This:** Prop drilling, particularly when the intermediate components do not utilize prop values.
**Why:** Reduces the complexity and coupling of intermediate components, simplifying code and improving maintainability. Context offers a direct way for deeply nested components to access needed data.
**Example:** (refer to the "useContext" example above)
## 2. Component Implementation
### 2.1. Functional Components
* **Do This:** Use functional components with Qwik's "component$" for all new components. Embrace immutability when working with props and state.
* **Don't Do This:** Use class-based components or mutable data structures without a clear reason.
**Why:** Functional components with hooks are simpler to read, write, and test. They promote immutability which makes state management easier and avoids unexpected side effects.
**Example:**
"""typescript
import { component$, useSignal } from '@builder.io/qwik';
export const MyComponent = component$(() => {
const name = useSignal('Initial Name');
return (
<p>Hello, {name.value}!</p>
(name.value = (event.target as HTMLInputElement).value)} />
);
});
"""
### 2.2. Props and Events
* **Do This:** Define prop types clearly using interfaces or types. Use Qwik's "Prop" to ensure type safety. Design events to be predictable and consistent. Use "$" to define event handlers.
* **Don't Do This:** Avoid "any" type for props. Don't introduce side effects within event handlers without proper handling.
**Why:** Type safety prevents runtime errors and improves code maintainability. Clear event handling reduces unexpected behavior and improves component predictability.
**Example:**
"""typescript
import { component$, Prop } from '@builder.io/qwik';
interface Props {
name: string;
age: number;
onClick: Prop<() => void>; // Correctly typed event handler
}
export const UserProfile = component$(({ name, age, onClick }) => {
return (
<p>Name: {name}</p>
<p>Age: {age}</p>
Click Me
);
});
"""
"""typescript
// Usage
import { component$ } from '@builder.io/qwik';
import { UserProfile } from './user-profile';
export const ParentComponent = component$(() => {
const handleClick = () => {
alert('Button clicked!');
};
return ;
});
"""
### 2.3. Code-Splitting
* **Do This:** Utilize Qwik's automatic code-splitting capabilities by breaking down larger components into smaller, lazy-loaded modules, keeping components small and focus on a specific task.
* **Don't Do This:** Bundle all component code into a single large file, which can hurt initial load times.
**Why:** Code splitting can improve the user experience for initial load times.
**Example:**
"""typescript
// Dynamically import components
import { component$, Dynamic } from '@builder.io/qwik';
export const ParentComponent = component$(() => {
return (
<p>Parent Component</p>
import('./lazy-component')} />
);
});
// lazy-component.tsx
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return <p>Lazy Loaded Component!</p>;
});
"""
### 2.4. Styling
* **Do This:** Use CSS Modules, Styled Components with Qwik Optimizer, or other CSS-in-JS solutions for component-specific styling. Scope styles to components to avoid naming conflicts.
* **Don't Do This:** Use global CSS styles that can inadvertently affect other parts of the application.
**Why:** Component-specific styling enhances maintainability and prevents unexpected style collisions. Use CSS Modules or CSS-in-JS to encapsulate styles within components.
**Example (CSS Modules):**
"""typescript
// my-component.tsx
import { component$ } from '@builder.io/qwik';
import styles from './my-component.module.css';
export const MyComponent = component$(() => {
return (
Hello, Qwik!
);
});
// my-component.module.css
.container {
background-color: #f0f0f0;
padding: 20px;
}
.title {
color: #333;
}
"""
### 2.5. Accessibility
* **Do This:** Ensure components are accessible by providing proper semantic HTML, ARIA attributes, and keyboard navigation. Test components with screen readers.
* **Don't Do This:** Ignore accessibility considerations, leading to components unusable by people with disabilities.
**Why:** Accessibility is crucial for creating inclusive applications. Providing correct semantic structure and ARIA attributes ensures screen readers and other assistive technologies can properly interpret the content.
**Example:**
"""typescript
import { component$ } from '@builder.io/qwik';
export const AccessibleButton = component$(() => {
return (
alert('Button clicked!')}>
Close
);
});
"""
### 2.6. Naming Conventions
* **Do This:** Use PascalCase for component names (e.g., "MyComponent"). Use camelCase for props and event handlers (e.g., "onClick", "userName").
* **Don't Do This:** Use inconsistent or cryptic names that obscure the component's purpose.
**Why:** Consistent naming conventions improve code readability and maintainability.
**Example:**
"""typescript
import { component$ } from '@builder.io/qwik';
interface Props {
userName: string;
onClick: () => void;
}
export const UserProfileCard = component$(({ userName, onClick }) => {
return (
<p>Welcome, {userName}!</p>
View Profile
);
});
"""
### 2.7. Component Composition
* **Do This:** Leverage component composition techniques to create flexible components. Use the "children" prop for content projection.
* **Don't Do This:** Create rigid components that are difficult to adapt to changing requirements.
**Why:** Component composition facilitates code reuse and allows components to be configured in various ways.
**Example:**
"""typescript
import { component$, Slot } from '@builder.io/qwik';
export const Card = component$(() => {
return (
);
});
// Usage:
import { component$ } from '@builder.io/qwik';
import { Card } from './card';
export const MyPage = component$(() => {
return (
This is the card body content.
Card Title
Save
);
});
"""
## 3. Performance Optimization
### 3.1. Minimizing Re-renders
* **Do This:** Use "useClientEffect$" and "useTask$" judiciously to minimize re-renders. Track only necessary dependencies to avoid triggering unnecessary updates. Freeze props to prevent changes.
* **Don't Do This:** Rely on naive state updates that cause frequent re-renders.
**Why:** Minimizing re-renders improves application's overall responsiveness. "useClientEffect$" is only executed in the client and can execute code that depends on the rendered state.
**Example:**
"""typescript
import { component$, useSignal, useTask$ } from '@builder.io/qwik';
export const OptimizedComponent = component$(() => {
const count = useSignal(0);
useTask$(({ track }) => {
// Track only the count value
track(() => count.value);
console.log('Count updated:', count.value);
});
return (
<p>Count: {count.value}</p>
count.value++}>Increment
);
});
"""
### 3.2. Serialization
* **Do This:** Properly serialize data passed as props to ensure resumability. Avoid passing complex, non-serializable data.
* **Don't Do This:** Pass functions or complex objects that cannot be serialized, which violates Qwik's resumability principle.
**Why:** Serialization is crucial for Qwik's resumability, which allows the application to pause and resume on the client.
**Example:**
"""typescript
import { component$ } from '@builder.io/qwik';
interface Props {
message: string; // Serializable data
}
export const SerializableComponent = component$(({ message }) => {
return <p>{message}</p>;
});
"""
### 3.3. Lazy Loading with "useVisibleTask$"
* **Do This:** Use "useVisibleTask$" for tasks that should only start when the component is visible, preventing unnecessary computations before the component is needed.
* **Don't Do This:** Perform heavy computations on component initialization regardless of visibility.
**Why:** "useVisibleTask$" only starts when the component is visible, avoiding unnecessary computations and improving initial load times.
"""typescript
import { component$, useVisibleTask$ } from '@builder.io/qwik';
export const VisibleComponent = component$(() => {
useVisibleTask$(({ track }) => {
// Track relevant state
track(() => true); // Example, replace with actual dependency
console.log('Component is visible!');
});
return <p>Visible Component</p>;
});
"""
## 4. Testing
### 4.1. Component Tests
* **Do This:** Write unit tests for individual components to ensure they render correctly and behave as expected. Use snapshot testing to catch unexpected UI changes. Integration testing is still necessary but is less relevant to this standard.
* **Don't Do This:** Skip component tests, relying solely on end-to-end tests.
**Why:** Unit tests provide rapid feedback on component behavior, making it easier to identify and fix bugs early in the development process.
**Example:** (using Jest and Qwik's testing utilities)
"""typescript
// my-component.test.tsx
import { render } from '@builder.io/qwik/testing';
import { MyComponent } from './my-component';
describe('MyComponent', () => {
it('should render correctly', async () => {
const { container } = await render();
expect(container.textContent).toContain('Hello, Qwik!');
});
});
"""
### 4.2. Types and Contracts
* **Do This:** use Typescript and type checking to make sure integration is secure. Follow a test-driven approach.
* **Don't Do This:** Avoid Typescript or testing because of time constraints.
**Why:** Avoid errors and ensure integration. Save time in debugging.
## 5. Security
### 5.1. Input Validation
* **Do This:** Validate all user inputs within components to prevent security vulnerabilities such as cross-site scripting (XSS) and injection attacks.
* **Don't Do This:** Trust user inputs without validation, leading to potential security exploits.
**Why:** Input validation is crucial for preventing malicious data from entering your application.
**Example:**
"""typescript
import { component$, useSignal } from '@builder.io/qwik';
export const ValidatedInput = component$(() => {
const inputValue = useSignal('');
const sanitizedValue = useSignal('');
const handleChange = (event: Event) => {
const input = (event.target as HTMLInputElement).value;
// Sanitize input value (example: remove HTML tags)
const sanitized = input.replace(/<[^>]*>/g, '');
inputValue.value = input;
sanitizedValue.value = sanitized;
};
return (
<p>Original Input: {inputValue.value}</p>
<p>Sanitized Input: {sanitizedValue.value}</p>
);
});
"""
### 5.2. Secure Data Handling
* **Do This:** Use secure methods for storing and transmitting sensitive data. Avoid storing sensitive data in local storage or cookies without proper encryption
* **Don't Do This:** Expose sensitive data directly in the client-side code.
**Why:** Protecting sensitive data is paramount. Secure data handling practices are essential for maintaining user privacy and security.
### 5.3. Avoiding Server-Side Rendering Vulnerabilities
* **Do This:** Sanitize data properly during SSR to avoid injection vulnerabilities.
* **Don't Do This:** Trust all data during SSR.
**Why:** When using SSR, the server needs to be just as secured as the client against vulnerabilities.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Testing Methodologies Standards for Qwik This document outlines the standards for testing methodologies in Qwik applications. Adhering to these standards ensures code quality, maintainability, and reliability within a Qwik project, specifically taking advantage of Qwik's unique features like resumability and lazy-loading. ## 1. General Testing Principles ### 1.1 Test Pyramid **Do This:** Follow the test pyramid strategy, emphasizing unit tests, followed by integration tests, and then end-to-end (E2E) tests. **Don't Do This:** Over-rely on E2E tests at the expense of unit and integration tests. **Why:** A well-balanced test pyramid reduces testing costs, increases confidence in the codebase, and enables faster feedback loops. Unit tests are the fastest and cheapest to run; focusing on them catches issues early. Excessive E2E tests are slow, brittle, and expensive to maintain. ### 1.2 Test-Driven Development (TDD) **Do This:** Consider implementing TDD, where tests are written before the actual code. **Don't Do This:** Write tests as an afterthought. **Why:** TDD forces you to think about the requirements and API design upfront, leading to cleaner, more testable code. ### 1.3 Test Organization **Do This:** Colocate tests with the component/module they are testing (e.g., "src/components/MyComponent/my-component.tsx" and "src/components/MyComponent/my-component.test.tsx"). Use a clear naming convention. **Don't Do This:** Place all tests in a single "tests/" directory without a logical structure. **Why:** Co-location improves discoverability and maintainability. It makes it easier to find and update tests when the component changes. ## 2. Unit Testing ### 2.1 Focus **Do This:** Unit tests should focus on testing individual functions, components, and services in isolation. Mock dependencies to avoid external dependencies and side effects. **Don't Do This:** Write unit tests that rely on external APIs, databases, or the DOM (except for rendering tests, which should be limited). **Why:** Unit tests provide fast feedback on the correctness of individual units of code. Isolation ensures that tests are not affected by external factors, making them more reliable. Qwik's resumability model means you need to specifically test the state management. ### 2.2 Tools **Do This:** Use Jest or Vitest as the primary unit testing frameworks. Utilize "@testing-library/react" (or "@testing-library/preact" under the hood in Qwik) for component rendering and interaction testing. Consider "msw" for mocking API calls. **Don't Do This:** Rely on outdated testing libraries or frameworks. **Why:** Jest and Vitest offer excellent performance, mocking capabilities, and a rich ecosystem of plugins. "@testing-library/react" encourages testing components from a user's perspective, improving test quality. "msw" gives a powerful framework for testing interactions. ### 2.3 Component Rendering Tests **Do This:** Use "@testing-library/react" to render components and assert on the rendered output. Focus on testing the component's public API and behavior. **Don't Do This:** Test implementation details or internal state directly. **Why:** Testing components from a user's perspective makes tests more resilient to changes in implementation details. Changes to the UI are more likely to require test updates than internal logic changes. **Example:** """tsx // src/components/Counter/counter.tsx import { component$, useSignal, $, useStylesScoped$ } from '@builder.io/qwik'; import styles from './counter.css?inline'; export const Counter = component$(() => { useStylesScoped$(styles); const count = useSignal(0); const increment = $(() => { count.value++; }); return ( <div class="counter"> <button onClick$={increment}>Increment</button> <span>{count.value}</span> </div> ); }); // src/components/Counter/counter.test.tsx import { render, screen, fireEvent } from '@testing-library/react'; import { h } from '@builder.io/qwik'; import { Counter } from './counter'; import { test, expect } from 'vitest'; test('increments the count on button click', async () => { render(h(Counter, {})); // Use h (Qwik's hyperscript) const button = await screen.findByText('Increment'); const countDisplay = screen.getByText('0'); fireEvent.click(button); expect(countDisplay).toHaveTextContent('1'); }); """ ### 2.4 Testing Qwik Specific Features **Do This:** Pay special attention to testing code within "$()" functions and "use*" hooks, since these are where the most Qwik-specific logic resides. Test your "routeLoader$" and "routeAction$" handlers effectively, mocking network requests and verifying state updates. **Don't Do This:** Assume Qwik's magic will automatically make everything work flawlessly. Write generic React tests that don't account for Qwik's resumability. **Why:** The "$()" functions and "use*" hooks are the core of the Qwik framework. Proper testing here makes sure the framework works correctly. ### 2.5 Mocking **Do This:** Use mocking appropriately to isolate units of code. Tools like "jest.mock" or "vi.mock" can be invaluable. Mock API calls using "msw" wherever possible. **Don't Do This:** Over-mock, which can lead to tests that don't reflect real-world behavior. ### 2.6 Example: Service Unit Test """typescript // src/services/api.ts export const fetchUserData = async (userId: string) => { const response = await fetch("/api/users/${userId}"); if (!response.ok) { throw new Error('Failed to fetch user data'); } return response.json(); }; // src/services/api.test.ts import { fetchUserData } from './api'; import { vi, expect, test } from 'vitest'; test('fetchUserData fetches user data successfully', async () => { const mockResponse = { id: '123', name: 'Test User' }; global.fetch = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve(mockResponse), }); const userData = await fetchUserData('123'); expect(userData).toEqual(mockResponse); expect(global.fetch).toHaveBeenCalledWith('/api/users/123'); }); test('fetchUserData throws an error when the request fails', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: false, }); await expect(fetchUserData('123')).rejects.toThrow('Failed to fetch user data'); }); """ ## 3. Integration Testing ### 3.1 Focus **Do This:** Integration tests should verify the interaction between different parts of the application, such as components working with services, or different modules interacting. **Don't Do This:** Treat integration tests as either large unit tests or small E2E tests. They should have a distinct purpose. **Why:** Integration tests ensure that different parts of the application work together correctly. ### 3.2 Tools **Do This:** Continue using Jest or Vitest for integration tests. Consider using "msw" to mock external API dependencies, and a lightweight testing server. **Don't Do This:** Attempting to use only unit testing tools **Why:** Leveraging good tools helps. ### 3.3 Component Integration Tests **Do This:** Test scenarios where multiple components interact. For instance, testing a form component that uses a service to submit data. **Don't Do This:** Primarily using only Jest or Vitest. **Example:** """tsx // src/components/Form/form.tsx import { component$, useSignal, $, useStylesScoped$ } from '@builder.io/qwik'; import { submitFormData } from '../../services/api'; //Example API call import styles from './form.css?inline'; export const Form = component$(() => { useStylesScoped$(styles); const name = useSignal(''); const email = useSignal(''); const submissionResult = useSignal<string | null>(null); const handleSubmit = $(async () => { try { await submitFormData({ name: name.value, email: email.value }); submissionResult.value = 'Form submitted successfully!'; } catch (error: any) { submissionResult.value = "Submission failed: ${error.message}"; } }); return ( <form onSubmit$={handleSubmit}> <div> <label htmlFor="name">Name:</label> <input type="text" id="name" value={name.value} onChange$={(e) => (name.value = e.target.value)} /> </div> <div> <label htmlFor="email">Email:</label> <input type="email" id="email" value={email.value} onChange$={(e) => (email.value = e.target.value)} /> </div> <button type="submit">Submit</button> {submissionResult.value && <div>{submissionResult.value}</div>} </form> ); }); // src/components/Form/form.test.tsx import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { h } from '@builder.io/qwik'; import { Form } from './form'; import { submitFormData } from '../../services/api'; //Example API call import { vi, expect, test } from 'vitest'; vi.mock('../../services/api'); // Mock the submitFormData function test('submits the form and displays success message', async () => { (submitFormData as any).mockResolvedValue(undefined); // Mock successful submission render(h(Form, {})); const nameInput = screen.getByLabelText('Name:'); const emailInput = screen.getByLabelText('Email:'); const submitButton = screen.getByText('Submit'); fireEvent.change(nameInput, { target: { value: 'Test Name' } }); fireEvent.change(emailInput, { target: { value: 'test@example.com' } }); fireEvent.click(submitButton); await waitFor(() => { expect(screen.getByText('Form submitted successfully!')).toBeInTheDocument(); }); expect(submitFormData).toHaveBeenCalledWith({ name: 'Test Name', email: 'test@example.com' }); }); test('displays an error message when submission fails', async () => { (submitFormData as any).mockRejectedValue(new Error('Submission failed!')); // Mock failed submission render(h(Form, {})); const nameInput = screen.getByLabelText('Name:'); const emailInput = screen.getByLabelText('Email:'); const submitButton = screen.getByText('Submit'); fireEvent.change(nameInput, { target: { value: 'Test Name' } }); fireEvent.change(emailInput, { target: { value: 'test@example.com' } }); fireEvent.click(submitButton); await waitFor(() => { expect(screen.getByText('Submission failed: Submission failed!')).toBeInTheDocument(); }); }); """ ### 3.4 Testing "routeLoader$" and "routeAction$" **Do This:** Write integration tests that verify the data fetching and mutation logic within your "routeLoader$" and "routeAction$" functions. Mock any backend dependencies. Verify that the UI updates correctly after the data loads or the action is performed. **Don't Do This:** Skip testing these critical server-side functions. **Why:** "routeLoader$" and "routeAction$" are the main entry points for data loading and form submissions in Qwik apps. Testing them makes sure that the application's data flow works correctly. ## 4. End-to-End (E2E) Testing ### 4.1 Focus **Do This:** E2E tests should simulate real user interactions with the application, covering critical user flows. Keep E2E tests focused on the most important functionality. **Don't Do This:** Use E2E tests to cover every possible scenario. This will lead to slow, brittle, and difficult-to-maintain test suites. **Why:** E2E tests provide the highest level of confidence in the application's correctness, but also the highest cost. They should focus on validating the core functionality from the user's perspective. ### 4.2 Tools **Do This:** Use Playwright or Cypress for E2E testing. These tools offer excellent browser automation capabilities and are well-suited for testing modern web applications. **Don't Do This:** Attempt manual testing when it can be automated. **Why:** These are industry leading tools to use ### 4.3 Test Environment **Do This:** Ensure that the E2E tests run against a dedicated test environment that is as close to production as possible. Seed the database with test data before each test run. **Don't Do This:** Run E2E tests against a development or production environment. **Why:** Running tests against a dedicated environment prevents interference with development and protects production data. ### 4.4 Writing Stable E2E Tests **Do This:** Write tests that are resilient to UI changes. Use semantic selectors (e.g., "data-testid") instead of CSS selectors that are likely to change. Wait for elements to be visible and interactable before interacting with them. Mock any backend dependencies that would cause inconsistencies. **Don't Do This:** Write tests that rely on specific CSS class names or IDs. Introduce artificial delays (e.g., "setTimeout") to wait for elements to load. Don't use "data-testid" at all. **Why:** Tests relying on CSS class are brittle. ### 4.5 Example E2E Test (Playwright) """typescript // tests/e2e/home.spec.ts import { test, expect } from '@playwright/test'; test('loads the homepage and displays the title', async ({ page }) => { await page.goto('/'); await expect(page.locator('h1')).toHaveText('Welcome to Qwik!'); }); test('navigates to the About page', async ({ page }) => { await page.goto('/'); await page.click('text=About'); await expect(page).toHaveURL('/about'); await expect(page.locator('h1')).toHaveText('About Us'); }); test('interacts with the counter', async ({ page }) => { await page.goto('/'); await page.getByRole('button', { name: 'Increment' }).click(); await expect(page.locator('span')).toHaveText('1'); }); """ ## 5. Types of Tests for Qwik Qwik applications benefit from tests specifically designed to address their unique characteristics: * **Resumability Tests:** Verify that the application state is correctly serialized and deserialized during resumability. Focus on testing state updates for components. * **Lazy-Loading Tests:** Verify that components and modules are lazy-loaded as expected, improving initial load time. This can be done by monitoring network requests during E2E tests. * **Optimizer Tests:** Verify that the Qwik optimizer is correctly splitting and chunking code, minimizing the initial download size. ## 6. Code Coverage **Do This:** Strive for high code coverage, but don't treat it as the only goal. Code coverage should be used as a tool to identify untested areas of the code, not as a measure of test quality. Aim for a coverage of at least 80%. **Don't Do This:** Aim for 100% code coverage at the expense of writing meaningful tests. **Why:** High code coverage reduces the risk of undiscovered bugs, but it doesn't guarantee that the code is correct. Meaningful tests that cover important use cases are more valuable than tests written solely to increase code coverage. ## 7. Continuous Integration (CI) **Do This:** Integrate tests into a CI pipeline to automatically run tests on every commit and pull request. **Don't Do This:** Rely on manual testing or running tests only before deployment. **Why:** CI ensures that tests are run consistently and provides early feedback on code changes, preventing regressions and improving code quality. ## 8. Test Data Management **Do This:** Use realistic test data that covers various scenarios, including edge cases and error conditions. **Don't Do This:** Use unrealistic or trivial test data that doesn't accurately reflect real-world usage. **Why:** Realistic test data helps uncover potential issues that might not be apparent with simple test cases. ## 9. Accessibility Testing **Do This:** Include accessibility testing in your testing strategy. Ensure the application meets accessibility standards. **Don't Do This:** Ignore accessibility concerns during the software development process. ## 10. Performance Testing **Do This:** Incorporate performance testing to identify and address performance bottlenecks. **Don't Do This:** Neglect performance testing until late in the development cycle. ## 11. Security Testing **Do This:** Conduct security tests to identify and mitigate security vulnerabilities. **Don't Do This:** Assume your application is inherently secure without proper security testing. These standards are designed to provide a solid foundation for testing Qwik applications. By following these guidelines, teams can ensure that they deliver high-quality, reliable, and maintainable software.
# Tooling and Ecosystem Standards for Qwik This document outlines the recommended tooling and ecosystem standards for Qwik development. Adhering to these guidelines ensures a consistent, maintainable, performant, and secure codebase. It's aimed at Qwik developers and serves as context for AI coding assistants. ## 1. Development Environment Setup ### 1.1 Use Node.js LTS and a Package Manager **Standard:** Use the latest LTS version of Node.js and a package manager like npm, pnpm or yarn. Node.js is used to execute Qwik Optimizer and Qwik City. **Why:** Utilizing the latest LTS version of Node.js guarantees stability, performance improvements, and access to the latest JavaScript features. A package manager ensures consistent dependency management across environments. **Do This:** * Install the latest LTS version of Node.js. Always download official Node binaries instead of package managers. * Use npm, pnpm or yarn for package management. pnpm is recommended for workspace projects. **Don't Do This:** * Use outdated Node.js versions. * Avoid global installations of dependencies. **Example:** """bash # Check Node.js version node -v # Check npm version npm -v #Check pnpm version pnpm -v #Check yarn version yarn -v """ ### 1.2 Editor and IDE Configuration **Standard:** Configure an editor or IDE with Qwik-specific extensions and pre-commit hooks for better consistency. **Why:** Proper editor configuration enhances developer productivity, catches errors early, and enforces consistent code style. Pre-commit hooks automate code formatting and linting before committing. **Do This:** * Use VS Code with the official Qwik extension. It provides syntax highlighting, code completion, and Qwik-specific diagnostics and quick fixes. * Install ESLint and Prettier extensions and configure them to format code on save. * Set up pre-commit hooks using Husky and lint-staged (or similar tools) to run linters and formatters before each commit. **Don't Do This:** * Rely solely on manual formatting. * Skip code reviews or automated checks. **Example:** ".vscode/settings.json": """json { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact" ], "files.eol": "\n" } """ "package.json": """json { "devDependencies": { "eslint": "^8.0.0", "eslint-config-qwik": "^1.0.0", "husky": "^8.0.0", "lint-staged": "^13.0.0", "prettier": "^2.0.0" }, "lint-staged": { "*.{js,jsx,ts,tsx,md,json}": "prettier --write" }, "husky": { "hooks": { "pre-commit": "lint-staged" } }, "prettier": { "arrowParens": "always", "semi": false, "trailingComma": "all", "singleQuote": true } } """ ### 1.3 Qwik CLI **Standard:** Utilize the Qwik CLI for creating and managing projects. **Why:** The Qwik CLI simplifies project setup, component generation, and building production-ready applications. **Do This:** * Install the Qwik CLI globally: "npm install -g @builder.io/qwik-city". * Use "qwik new" to scaffold new Qwik projects. * Use "qwik add" to add integrations like TailwindCSS or Cloudflare. * Leverage the CLI for building: "npm run build" **Don't Do This:** * Manually configure project structures where CLI tools are available. **Example:** """bash # Create a new Qwik City project npm create qwik@latest my-qwik-app # Add TailwindCSS integration to an existing Qwik City project cd my-qwik-app npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p """ ## 2. Linting and Formatting ### 2.1 ESLint Configuration **Standard:** Use ESLint with recommended Qwik-specific rules. **Why:** ESLint helps catch syntax errors, enforce code style, and ensure code quality throughout the project. Qwik-specific rules enforce best practices related to Qwik APIs and patterns. **Do This:** * Install ESLint and the "eslint-config-qwik" package. * Extend the recommended Qwik configuration in your ".eslintrc.js" file. * Configure ESLint to run on file save within your editor. * Address all linting errors and warnings. **Don't Do This:** * Disable or ignore ESLint rules without justification. * Commit code with linting errors. **Example:** ".eslintrc.js": """javascript module.exports = { root: true, extends: ['qwik', 'prettier'], // Removed 'plugin:@typescript-eslint/recommended' parserOptions: { ecmaVersion: 'latest', sourceType: 'module', }, rules: { // Add custom rules here. For example: // 'no-console': 'warn', }, }; """ ### 2.2 Prettier Configuration **Standard:** Use Prettier for consistent code formatting. **Why:** Prettier automatically formats code based on a predefined set of rules, ensuring a uniform style across the project. It removes subjective formatting debates. **Do This:** * Install Prettier. * Create a ".prettierrc.js" file in the root of your project. * Configure Prettier rules to enforce consistent formatting conventions (e.g., single quotes, trailing commas). * Integrate Prettier with your editor and pre-commit hooks. **Don't Do This:** * Use inconsistent formatting conventions. * Override Prettier's formatting without a clear reason. **Example:** ".prettierrc.js": """javascript module.exports = { arrowParens: 'always', semi: false, trailingComma: 'all', singleQuote: true, printWidth: 100, //Consider this option as it becomes more common }; """ ### 2.3 TypeScript Integration **Standard**: Use TypeScript for type safety and improved code maintainability. **Why**: TypeScript enforces static typing, which helps catch errors during development, improves code readability, and facilitates easier refactoring. **Do This**: * Install TypeScript as a dev dependency: "npm install --save-dev typescript". * Configure TypeScript to enforce strict type checking by enabling "strict" mode in "tsconfig.json". * Define types for all components, services, and data structures. **Don't Do This**: * Use "any" type excessively. * Ignore TypeScript errors and warnings. **Example**: "tsconfig.json": """json { "compilerOptions": { "target": "esnext", "module": "esnext", "moduleResolution": "node", "strict": true, "jsx": "preserve", "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "isolatedModules": true, "baseUrl": ".", "paths": { "*": ["./src/*"] } }, "include": ["src/**/*"], "exclude": ["node_modules"] } """ ## 3. Package Management and Dependencies ### 3.1 Dependency Versioning **Standard:** Specify dependency versions precisely using semantic versioning (semver). **Why:** Semver ensures predictable dependency updates and prevents unexpected breaking changes. **Do This:** * Use specific version numbers or version ranges with caution (e.g., "^1.2.3", "~1.2.3"). Consider exact pinning for library codebases. * Regularly review and update dependencies to benefit from bug fixes and new features. **Don't Do This:** * Use wildcard version ranges (e.g., "*"). * Ignore dependency update notifications. **Example:** "package.json": """json { "dependencies": { "@builder.io/qwik": "1.4.0", "@builder.io/qwik-city": "1.4.0" }, "devDependencies": { "typescript": "~5.0.0" } } """ ### 3.2 Managing Dependencies **Standard:** Favor ES Modules-compatible packages to leverage tree-shaking. **Why:** Using ES Modules allows the bundler to include only the necessary code, reducing the bundle size and improving performance. **Do This:** * Prefer packages that provide ES Module versions. * Use dynamic "import()" for optional dependencies or large modules. * Analyze bundle size using tools like "rollup-plugin-visualizer". **Don't Do This:** * Use CommonJS modules in new projects or when ES Module alternatives are available. * Import entire libraries when only a subset of functions is needed. **Example:** """typescript // Use dynamic import for optional dependencies async function loadAnalytics() { if (process.env.NODE_ENV === 'production') { const analytics = await import('analytics'); analytics.init({ /* config */ }); } } """ ### 3.3 Recommended Qwik Libraries **Standard:** Utilize the Qwik ecosystem libraries when appropriate. **Why:** Qwik ecoystems provides a well-tested and optimized foundation for common tasks. **Do This:** * Use "@builder.io/qwik-city" for routing and server-side rendering. * Explore "@builder.io/qwik-react" for integrating React components into Qwik applications. * Utilize "@qwikdev/headroom" for header visibility management **Don't Do This:** * Reinvent the wheel by creating functionality already available in the Qwik ecosystem (after evaluation). * Use libraries without understanding their impact on loading and performance. ### 3.4 Handling Environment Variables **Standard**: Use environment variables for configuration to avoid hardcoding sensitive information and to configure different environments. **Why**: Environment variables keep configuration separate from code, enhancing security and flexibility across different deployment environments. **Do This**: * Use ".env" files for local development using libraries like "dotenv". * Access environment variables using "process.env.VARIABLE_NAME". * Define environment variables in deployment environments (e.g., Netlify, Vercel). **Don't Do This**: * Commit ".env" files to version control. * Hardcode sensitive information (API keys, database credentials) in the code. **Example**: """bash # .env file API_KEY=your_api_key BASE_URL=https://api.example.com """ """typescript // Accessing in Qwik component import { $, useStore } from '@builder.io/qwik'; export const MyComponent = component$(() => { const apiKey = process.env.API_KEY; const baseUrl = process.env.BASE_URL; // ... use apiKey and baseUrl in the component }); """ ## 4. Testing ### 4.1 Testing Frameworks **Standard**: Employ testing frameworks such as Jest or Vitest for writing unit and integration tests. **Why**: Testing frameworks provide a structure for writing and running tests, enabling automated verification of component and application behavior. **Do This**: * Set up Jest or Vitest with appropriate Qwik-aware configurations. * Write unit tests for individual components to verify their behavior in isolation. * Write integration tests to ensure that components work correctly together. * Aim for high test coverage to catch bugs early in the development cycle. **Don't Do This**: * Skip writing tests or postpone testing until late in the development cycle. * Write tests that are brittle and tightly coupled to implementation details. **Example**: """javascript // Example test using Vitest import { test, expect } from 'vitest'; import { MyComponent } from './my-component'; import { render } from '@builder.io/qwik/testing'; test('MyComponent renders correctly', async () => { const { container } = await render(<MyComponent />); expect(container.textContent).toContain('Hello, Qwik!'); }); """ ### 4.2 End-to-End (E2E) Testing **Standard**: Utilize E2E testing tools like Cypress or Playwright to simulate user interactions and verify application functionality. **Why**: E2E tests validate the entire application flow, ensuring that all components and services work together as expected from the user's perspective. **Do This**: * Set up Cypress or Playwright to automate browser-based testing. * Write E2E tests that simulate common user scenarios. * Run E2E tests in a continuous integration environment to catch integration issues early. **Don't Do This**: * Rely solely on manual testing without automated E2E tests. * Ignore E2E test failures or postpone fixing them. **Example:** """javascript // Example test using Playwright import { test, expect } from '@playwright/test'; test('Homepage has title', async ({ page }) => { await page.goto('http://localhost:3000/'); await expect(page).toHaveTitle("Welcome to Qwik City!"); }); """ ## 5 Continuous Integration and Deployment ### 5.1 CI/CD Pipelines **Standard**: Set up CI/CD pipelines using platforms like GitHub Actions, GitLab CI, or Jenkins to automate building, testing, and deploying applications. **Why**: CI/CD pipelines automate software delivery, reducing manual effort, improving reliability, and accelerating the release cycle. **Do This**: * Configure CI/CD pipelines to trigger on code commits and pull requests. * Run linters, formatters, and tests as part of the CI/CD pipeline. * Automate the deployment process to staging and production environments. **Don't Do This**: * Manually build, test, and deploy applications. * Skip CI/CD pipelines or neglect to maintain them. **Example**: ".github/workflows/deploy.yml": """yaml name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '18' # Use a supported Node version - run: npm install - run: npm run build - name: Deploy to Netlify uses: netlify/actions/deploy@v1 env: NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} PRODUCTION_DEPLOY: true """ ### 5.2 Cloud Platforms **Standard**: Deploy Qwik applications to cloud platforms like Netlify, Vercel, Cloudflare Pages, or AWS Amplify for optimal performance and scalability. **Why**: Cloud platforms offer features such as edge caching, serverless functions, and automated deployments, which enhance application performance and scalability. **Do This**: * Choose a cloud platform that aligns with your application's requirements. * Configure deployment settings to optimize performance (e.g., enable CDN, configure caching). * Monitor application performance using cloud platform monitoring tools. **Don't Do This**: * Host applications on outdated infrastructure. * Ignore performance monitoring or fail to address performance issues. ## 6. Code Analysis and Monitoring ### 6.1 Static Code Analysis **Standard**: Utilize static code analysis tools such as SonarQube or CodeClimate to identify potential code quality issues. **Why**: Static code analysis helps detect bugs, security vulnerabilities, and code smells early in the development cycle. **Do This**: * Integrate static code analysis tools into the CI/CD pipeline. * Enforce code quality rules and address violations promptly. **Don't Do This**: * Ignore static code analysis findings or postpone fixing them. ### 6.2 Performance Monitoring **Standard**: Implement performance monitoring using tools like Sentry or New Relic to track application performance in production. **Why**: Performance monitoring allows you to identify bottlenecks, diagnose performance issues in real-time, and optimize application performance. **Do This**: * Integrate performance monitoring tools into the application. * Set up alerts to notify you of performance regressions or errors. * Regularly review performance metrics and optimize code accordingly. **Don't Do This**: * Neglect to monitor performance. **Example**: """typescript // Integrating Sentry into a Qwik application import * as Sentry from '@sentry/qwik'; import { init } from '@qwik-city/vite'; init({ dsn: 'YOUR_SENTRY_DSN', integrations: [ new Sentry.BrowserTracing(), new Sentry.Replay(), ], // Performance Monitoring tracesSampleRate: 0.1, // Capture 10% of transactions for performance monitoring. // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. }); """ ## 7. Security ### 7.1 Dependency Security Scanning **Standard**: Use tools like "npm audit", "yarn audit", or Snyk to scan dependencies for security vulnerabilities. **Why**: Dependency scanning identifies known vulnerabilities in third-party libraries, helping you avoid including insecure code in your application. **Do This**: * Run dependency scans regularly as part of the CI/CD pipeline. * Update vulnerable dependencies promptly. * Consider using a dependency firewall to block the introduction of vulnerable dependencies. **Don't Do This**: * Ignore dependency security vulnerabilities. * Fail to update vulnerable dependencies. ### 7.2 Code Security Audits **Standard**: Conduct code security audits to identify potential security flaws in the codebase. **Why**: Code security audits reveal vulnerabilities such as injection flaws, authentication issues, and cross-site scripting (XSS) vulnerabilities, helping you prevent security breaches. **Do This**: * Engage security experts to perform code security audits. * Address all identified security vulnerabilities promptly. * Implement security best practices, such as input validation, output encoding, and secure authentication. ### 7.3 Secure Configuration **Standard**: Follow secure configuration practices to protect sensitive data and prevent unauthorized access. **Why**: Secure configuration helps prevent accidental exposure of sensitive data and reduces the risk of security breaches. **Do This**: * Use environment variables to store sensitive configuration data. * Encrypt sensitive data at rest and in transit. * Implement access controls and authentication mechanisms to restrict unauthorized access.
# Core Architecture Standards for Qwik This document outlines the core architectural standards for Qwik applications. It focuses on establishing a robust and maintainable foundation for Qwik projects, emphasizing key architectural patterns, project structure, and organization principles that align with Qwik's unique resumability and performance characteristics. This document aims to guide developers in building scalable, efficient, and maintainable Qwik applications. ## 1. Fundamental Architectural Patterns ### 1.1. Component-Based Architecture **Standard:** Embrace a component-based architecture where the application UI is decomposed into reusable and independent components. * **Do This:** Design interfaces and define clear contracts for components, promoting modularity and testability. * **Don't Do This:** Create monolithic components that handle excessive responsibilities and dependencies. **Why:** Component-based architecture promotes code reuse, simplifies testing, and enhances maintainability. **Example:** """tsx // Good: Small, focused component import { component$ } from '@builder.io/qwik'; export const Button = component$((props: { label: string, onClick$: () => void }) => { return <button onClick$={props.onClick$}>{props.label}</button>; }); // Bad: Large, complex component with multiple responsibilities export const ComplexComponent = component$(() => { // ... lots of logic and multiple UI elements return ( <div> {/* ... */} </div> ); }); """ ### 1.2. Reactive Programming **Standard:** Leverage Qwik's built-in reactivity features for managing application state and data flow. * **Do This:** Utilize signals ("useSignal", "useStore") effectively to create reactive data bindings. * **Don't Do This:** Directly manipulate the DOM or rely on global mutable state. **Why:** Reactive programming with signals enhances performance, reduces boilerplate, and makes the application more predictable. **Example:** """tsx // Correct usage of signals import { component$, useSignal } from '@builder.io/qwik'; export const Counter = component$(() => { const count = useSignal(0); return ( <div> <button onClick$={() => count.value++}>Increment</button> {count.value} </div> ); }); // Anti-pattern: Directly manipulating the DOM (Avoid) export const CounterBad = component$(() => { return ( <div> <button onClick$={() => { const element = document.getElementById('counter'); if (element) element.textContent = parseInt(element.textContent || '0') + 1 + "";} }>Increment</button> <span id='counter'>0</span> </div> ); }); """ ### 1.3. State Management **Standard:** Adopt a well-defined state management strategy, utilizing "useStore" for complex state requirements and "useSignal" for simple, transient state. * **Do This:** Organize store data in a structured manner that aligns with the application's data model. * **Don't Do This:** Overuse global state when component-level state suffices. **Why:** Centralized state management promotes data consistency, simplifies debugging, and improves the maintainability of complex applications. **Example:** """tsx // Using useStore (for Complex State) import { component$, useStore } from '@builder.io/qwik'; interface User { name: string; age: number; } export const UserProfile = component$(() => { const user = useStore<User>({ name: 'John Doe', age: 30, }); return ( <div> <p>Name: {user.name}</p> <p>Age: {user.age}</p> </div> ); }); """ ### 1.4. Resumability **Standard:** Leverage Qwik's core principle of resumability to enhance application performance and reduce initial load time. * **Do This:** Design the application to minimize the amount of JavaScript that needs to be downloaded and executed upfront. * **Don't Do This:** Load lots of unnecessary javascript which are not used on initial load. **Why:** Resumability significantly improves the user experience by providing faster initial page loads and improved time-to-interactive. **Example:** """tsx // Correct : The event handler is not serialized. It will only be bundled and transmitted to the browser when user performs interaction on this component in the browser. import { component$, useSignal } from '@builder.io/qwik'; export const DelayedCounter = component$(() => { const count = useSignal(0); return ( <div> <button onClick$={() => count.value++}>Increment</button> <span>{count.value}</span> </div> ); }); """ ### 1.5. Folder Structure **Standard:** Follow a clear and consistent project structure to organize components, services, and other application assets. * **Do This:** Organize code into logical folders like "components", "services", "routes", and "utils". * **Don't Do This:** Place all files in a single directory or use inconsistent naming conventions. **Why:** Consistent project structure enhances navigability and maintainability. **Example:** """ src/ ├── components/ # Reusable UI components │ ├── Button/ │ │ ├── Button.tsx │ │ └── Button.css │ └── Card/ │ ├── Card.tsx │ └── Card.css ├── pages/ # Route-specific components │ ├── index.tsx │ └── about.tsx ├── services/ # Business logic and data fetching │ └── api.ts ├── utils/ # Utility functions and helpers │ └── helpers.ts └── root.tsx # Root qwik component. Defines the sites layout """ ## 2. Project Organization Principles ### 2.1. Separation of Concerns **Standard:** Maintain a clear separation of concerns (SoC) between UI, logic, and data management. * **Do This:** Move business logic away from UI. Create dedicated services (e.g. "api.ts") for data fetching. * **Don't Do This:** Mix UI rendering with complex calculations or data manipulation. **Why:** SoC simplifies debugging, facilitates testing, and promotes code reuse. **Example:** """tsx // Good: Business logic separated from UI import { component$ } from '@builder.io/qwik'; import { fetchUserData } from './services/api'; export const UserProfile = component$(() => { const userData = fetchUserData(); // Asynchronous data fetching return ( <div> <p>Name: {userData.name}</p> <p>Age: {userData.age}</p> </div> ); }); // In services/api.ts export const fetchUserData = async () => { //Logic relating to fetching from API endpoint. return { name: 'Example', age: 25 }; }; // Bad: Mixing UI rendering with data manipulation export const UserProfileBad = component$(() => { const userData = {name: 'Example', age: 25}; //data hardcoded const processedData = "${userData.name} - ${userData.age}"; return ( <div> {processedData} </div> ); }); """ ### 2.2. Atomic Design **Standard:** Consider adopting the principles of Atomic Design to structure components. * **Do This:** Organize components into atoms, molecules, organisms, templates, and pages. * **Don't Do This:** Create ad-hoc component structures without a clear organizing principle. **Why:** Atomic Design provides a structured approach for building UIs, enhancing component reuse and maintainability. **Example:** """ components/ ├── atoms/ # Smallest, indivisible components (e.g., buttons, labels, inputs) │ ├── Button.tsx │ └── Input.tsx ├── molecules/ # Simple combinations of atoms (e.g., input with label) │ └── SearchBar.tsx ├── organisms/ # Relatively complex sections of the interface (e.g., a header) │ └── Header.tsx ├── templates/ # Page-level layouts that stitch organisms together │ └── HomeTemplate.tsx └── pages/ # Actual pages using templates └── index.tsx """ ### 2.3. Context API for Global Data **Standard:** Use the Context API for sharing data that is considered "global" for a tree of Qwik components. * **Do This:** Make a new context with "useContextProvider" to share data for components that are nested deeply. * **Don't Do This:** Share data by passing props through several layers of components ("prop drilling"). **Why:** Context API helps in ensuring maintainability for sharing "global" data in React applications. **Example:** """tsx //Create the context import { createContextId } from "@builder.io/qwik"; interface ThemeContextType { theme?: "light" | "dark"; setTheme?: (theme: ThemeContextType["theme"]) => void; } export const ThemeContext = createContextId<ThemeContextType>("theme-context"); //Wrap children with theme context import { component$, useContextProvider, useSignal } from "@builder.io/qwik"; import { ThemeContext } from "./theme-context"; export const ThemeProvider = component$((props: { children: any }) => { const themeSig = useSignal<ThemeContextType["theme"]>("light"); useContextProvider(ThemeContext, { theme: themeSig.value, setTheme: (theme) => { themeSig.value = theme; }, }); return props.children; }); //Consume theme from a deeply nested component import { component$, useContext } from "@builder.io/qwik"; import { ThemeContext } from "./theme-context"; export const DeeplyNestedComponent = component$(() => { const themeContext = useContext(ThemeContext); return ( <div> Theme: {themeContext.theme} <button onClick$={() => themeContext.setTheme?.(themeContext.theme === "light" ? "dark" : "light")}> Toggle Theme </button> </div> ); }); """ ## 3. Qwik-Specific Considerations ### 3.1 Optimizer usage **Standard:** Ensure that Qwik's optimizer is configured correctly to remove unused code, inline critical CSS, and perform other performance optimizations. * **Do This:** Use the Qwik CLI to initiate projects. This will automatically configures the optimizer. * **Don't Do This:** Skip this step when creating a production build. **Why:** The optimizer minimizes the bundle size, improves initial load performance, and reduces time to interactive (TTI). ### 3.2 Prefetching and lazy loading **Standard:** Strategically implement prefetching for critical resources and lazy loading for non-critical assets. * **Do This:** Employ "useClientEffect$" and "useVisibleTask$" to prefetch data or assets needed shortly after initial load. * **Don't Do This:** Eagerly load all assets upfront. **Why:** Prefetching and lazy loading optimize resource utilization and further enhance performance. **Example:** """tsx //Component is visible load data import { component$, useVisibleTask$ } from '@builder.io/qwik'; import { fetchData } from './services/api'; export const DataComponent = component$(() => { useVisibleTask$(async () => { const data = await fetchData(); // Fetch data when the component becomes visible // use the data }); return ( <div> {/* Render the Data */} </div> ); }); """ ### 3.3 Using Qwik City **Standard:** When building full-fledged applications, leverage Qwik City for routing, data loading, and server-side rendering (SSR). * **Do This:** Follow Qwik City's conventions for defining routes, layouts, and data endpoints. * **Don't Do This:** Manually handle routing and server-side logic in complex applications. **Why:** Qwik City automates common tasks, improving development speed, performance, and SEO. ### 3.4 File system routing **Standard:** Use the Qwik City's file system routing as a key aspect of the core architecture of the Qwik application. * **Do This:** Create files inside the "/routes" directory. These files will then be available as routes in the Qwik App. * **Don't Do This:** Create routes in any other way other than creating files inside the "/routes" directory and expect the routes to work. **Why:** File system routing allows developers to create routes in a Qwik application using the file system, rather than manual route creation. **Example:** """text src/ └── routes/ ├── index.tsx # -> / ├── about/ # -> /about/ └── contact.tsx # -> /contact """ ## 4. Security Best Practices ### 4.1. Secure Coding Practices **Standard:** Adhere to secure coding practices to prevent common web vulnerabilities. * **Do This:** Sanitize user inputs, implement proper authentication and authorization, and prevent cross-site scripting (XSS) and cross-site request forgery (CSRF) attacks. * **Don't Do This:** Trust user input without validation or expose sensitive information in client-side code. **Why:** Security vulnerabilities can compromise the application's integrity and user data. ### 4.2. Environment Variables **Standard:** Securely manage environment variables to avoid exposing sensitive information in source code. * **Do This:** Use ".env" files for development environments and configure environment variables in production deployments. * **Don't Do This:** Hard-code API keys or passwords directly in the source code. **Why:** Environment variables help keep sensitive data separate from the code base. ### 4.3. Dependency Management **Standard:** Manage dependencies effectively and keep packages up to date. * **Do This:** Use a package manager (e.g., npm, yarn, pnpm) to manage dependencies and regularly update packages to address security vulnerabilities. * **Don't Do This:** Use outdated or unmaintained packages. **Why:** Vulnerable dependencies can introduce security risks. ## 5. Common Anti-Patterns * **Over-reliance on Global State:** Avoid using global state excessively. Prefer component-level state or context when possible. * **Direct DOM Manipulation:** Avoid directly manipulating the DOM. Use Qwik's reactive features instead. * **Ignoring Qwik’s Resumability:** Neglecting to optimize for resumability can negate many of Qwik's performance benefits. * **Complex Components:** Large, overly complex components are difficult to maintain and test. Break them down into smaller, reusable parts. * **Inconsistent Folder Structure:** A disorganized folder structure makes it difficult to navigate and maintain the codebase. ## 6. Continuous Improvement This document will be updated periodically to reflect the latest Qwik features, best practices, and community standards. Contributors are encouraged to suggest improvements and refinements to ensure this document remains a valuable resource for Qwik developers.
# Code Style and Conventions Standards for Qwik This document outlines the coding style and conventions standards for Qwik projects, aiming to ensure consistency, readability, and maintainability across the codebase. These standards are designed to be used in conjunction with other rules in this series (e.g., architecture, security), providing a comprehensive guide for Qwik development. These conventions help create code that is easy to understand, debug, and extend, while also leveraging Qwik's unique features for optimal performance. ## 1. General Formatting Consistent formatting is crucial for code readability. Qwik projects should adhere to the following general formatting rules: ### 1.1. Whitespace * **Do This:** * Use 2 spaces for indentation. This helps make your code more readable on smaller screens and promotes a consistent visual structure. """typescript // Good: 2 spaces for indentation export const MyComponent = component$(() => { return ( <div> <p>Hello, Qwik!</p> </div> ); }); """ * **Don't Do This:** * Avoid using tabs for indentation. This can lead to inconsistent formatting across different editors and environments. """typescript // Bad: Tabs for indentation export const MyComponent = component$(() => { return ( <div> <p>Hello, Qwik!</p> </div> ); }); """ * **Why:** Consistent indentation improves code readability and reduces visual clutter. Using spaces ensures consistency across different development environments. ### 1.2. Line Length * **Do This:** * Limit lines to a maximum of 120 characters. Longer lines can be difficult to read and manage. """typescript // Good: Lines under 120 characters export const LongComponentName = component$(() => { return ( <div style={{ width: '100%', backgroundColor: 'lightblue', padding: '10px' }}> <p>This is a component with a long name and some styling.</p> </div> ); }); """ * **Don't Do This:** * Avoid excessively long lines that wrap around the editor. """typescript // Bad: Lines exceeding 120 characters export const LongComponentName = component$(() => { return ( <div style={{ width: '100%', backgroundColor: 'lightblue', padding: '10px', border: '1px solid gray', margin: '5px' }}> <p>This is a component with a long name and some styling. It should be avoided to exceed line limits.</p> </div> ); }); """ * **Why:** Limiting line length improves readability, especially on smaller screens or when comparing different versions of code. ### 1.3. Blank Lines * **Do This:** * Use blank lines to separate logical sections of code. This visually groups related code and improves readability. """typescript // Good: Blank lines for separation export const MyComponent = component$(() => { // Data fetching logic const data = useStore({ name: 'Qwik' }); // Rendering the component return ( <div> <p>Hello, {data.name}!</p> </div> ); }); """ * **Don't Do This:** * Avoid excessive blank lines that create large gaps in the code. """typescript // Bad: Excessive blank lines export const MyComponent = component$(() => { // Data fetching logic const data = useStore({ name: 'Qwik' }); // Rendering the component return ( <div> <p>Hello, {data.name}!</p> </div> ); }); """ * **Why:** Judicious use of blank lines helps to visually organize code into logical blocks, making it easier to understand the structure and flow. ### 1.4. Trailing Commas * **Do This:** * Include trailing commas in multi-line object literals, array literals, and function parameter lists. This simplifies adding, removing, or reordering items. """typescript // Good: Trailing commas const config = { name: 'My App', version: '1.0.0', author: 'John Doe', // Trailing comma }; """ * **Don't Do This:** * Omit trailing commas, as this can lead to unnecessary diffs in version control systems """typescript // Bad: Missing trailing comma const config = { name: 'My App', version: '1.0.0', author: 'John Doe' }; """ * **Why:** Trailing commas make version control diffs cleaner when adding, removing, or reordering items in lists and objects. ### 1.5. Ending Files * **Do This:** * End every file on a newline. ## 2. Naming Conventions Clear and consistent naming conventions are essential for understanding the purpose of different elements in the codebase. ### 2.1. Variables * **Do This:** * Use camelCase for variable names. This convention is widely used in JavaScript and makes variables easily identifiable. """typescript // Good: camelCase variable names const userName = 'John Doe'; const itemCount = 10; """ * **Don't Do This:** * Avoid snake_case or PascalCase for variable names. """typescript // Bad: snake_case variable names const user_name = 'John Doe'; // Bad: PascalCase variable names const UserName = 'John Doe'; """ * **Why:** camelCase is the standard convention in JavaScript and enhances code consistency. ### 2.2. Constants * **Do This:** * Use UPPER_SNAKE_CASE for constants. This clearly distinguishes constants from variables. """typescript // Good: UPPER_SNAKE_CASE constants const API_URL = 'https://example.com/api'; const MAX_ITEMS = 20; """ * **Don't Do This:** * Avoid camelCase or PascalCase for constants. """typescript // Bad: camelCase constants const apiUrl = 'https://example.com/api'; // Bad: PascalCase constants const ApiUrl = 'https://example.com/api'; """ * **Why:** Using UPPER_SNAKE_CASE for constants makes it immediately clear that these values should not be modified. ### 2.3. Functions * **Do This:** * Use camelCase for function names. This maintains consistency with variable naming. """typescript // Good: camelCase function names function calculateTotal(price: number, quantity: number): number { return price * quantity; } const handleClick = () => { console.log('Button clicked!'); }; """ * **Don't Do This:** * Avoid PascalCase or snake_case for function names. """typescript // Bad: PascalCase function names function CalculateTotal(price: number, quantity: number): number { return price * quantity; } // Bad: snake_case function names function calculate_total(price: number, quantity: number): number { return price * quantity; } """ * **Why:** camelCase is the standard convention in JavaScript and enhances code consistency. ### 2.4. Components * **Do This:** * Use PascalCase for component names. This distinguishes components from regular functions or variables. """typescript // Good: PascalCase component names export const MyComponent = component$(() => { return <p>Hello, Qwik!</p>; }); """ * **Don't Do This:** * Avoid camelCase or snake_case for component names. """typescript // Bad: camelCase component names export const myComponent = component$(() => { return <p>Hello, Qwik!</p>; }); // Bad: snake_case component names export const my_component = component$(() => { return <p>Hello, Qwik!</p>; }); """ * **Why:** PascalCase is the standard convention for React components (and adopted by Qwik), ensuring consistent naming across the codebase. ### 2.5. Files and Directories * **Do This:** * Use kebab-case for file and directory names related to components or routes. Consistent naming makes the project structure more predictable. """ // Good: kebab-case file and directory names src/ └── components/ ├── my-component/ │ ├── my-component.tsx │ └── my-component.css └── other-component/ ├── other-component.tsx └── other-component.css """ * **Don't Do This:** * Avoid camelCase or snake_case for file and directory names. """ // Bad: camelCase file and directory names src/ └── components/ ├── myComponent/ │ ├── myComponent.tsx │ └── myComponent.css └── otherComponent/ ├── otherComponent.tsx └── otherComponent.css """ * **Why:** kebab-case is a common convention for file and directory names in web development, contributing to a more consistent project structure. ## 3. Qwik-Specific Conventions Qwik introduces specific concepts like "component$", "useStore", and "useTask$". Following specific conventions for these elements is essential. ### 3.1. "component$" * **Do This:** * Use "component$" for creating components that leverage Qwik's resumability features. Always lazy-load components using "component$". """typescript // Good: Using component$ for lazy-loaded components import { component$ } from '@builder.io/qwik'; export const MyComponent = component$(() => { return <p>Hello, Qwik!</p>; }); """ * **Don't Do This:** * Avoid using regular function declarations for creating components that should be resumable. Also, avoid defining components inline with other logic. """typescript // Bad: Not using component$ export function OldComponent() { return <p>Hello, Old Qwik!</p>; } """ * **Why:** "component$" ensures that your components are lazy-loaded and resumable, improving performance and initial load time. ### 3.2. "useStore" * **Do This:** * Use "useStore" for managing component state that needs to be persisted across resumability boundaries. """typescript // Good: Using useStore for component state import { component$, useStore } from '@builder.io/qwik'; export const MyComponent = component$(() => { const store = useStore({ name: 'Qwik', count: 0, }); return ( <div> <p>Hello, {store.name}! Count: {store.count}</p> </div> ); }); """ * **Don't Do This:** * Avoid using regular "useState" from React, as it might not be compatible with Qwik's resumability features. """typescript // Bad: Using useState (React) import { component$ } from '@builder.io/qwik'; import { useState } from 'react'; export const MyComponent = component$(() => { const [name, setName] = useState('Qwik'); // This is wrong in Qwik. return ( <div> <p>Hello, {name}!</p> </div> ); }); """ * **Why:** "useStore" is designed to work with Qwik's resumability, ensuring that your component state is correctly persisted and restored. It provides efficient serialization and deserialization for optimal performance. ### 3.3. "useTask$" * **Do This:** * Use "useTask$" to perform side effects that need to run after rendering, and to re-run them whenever specific dependencies change. """typescript // Good: Using useTask$ for side effects import { component$, useTask$ } from '@builder.io/qwik'; export const MyComponent = component$(() => { useTask$(() => { console.log('Component rendered!'); }); return <p>Hello, Qwik!</p>; }); """ * **Don't Do This:** * Avoid using "useClientEffect$" for tasks better suited for execution after server-side rendering or during rehydration on the client, as "useClientEffect$" runs only on the client. Direct DOM manipulation within components (except within "useTask$") """typescript // Bad: Using useClientEffect$ when not needed import { component$, useClientEffect$ } from '@builder.io/qwik'; export const MyComponent = component$(() => { useClientEffect$(() => { console.log('Component rendered on client!'); }); return <p>Hello, Qwik!</p>; }); """ * **Why:** "useTask$" ensures that side effects are properly handled during server-side rendering and client-side rehydration, maintaining consistency and avoiding unexpected behavior. ### 3.4 "useSignal" * **Do This:** * Use "useSignal" when you need reactivity at a more granular level, rather than for entire component state. Signals can be used for tracking very specific values that trigger updates. """typescript import { component$, useSignal } from '@builder.io/qwik'; export const MyComponent = component$(() => { const count = useSignal(0); return ( <div> <p>Count: {count.value}</p> <button onClick$={() => count.value++}>Increment</button> </div> ); }); """ * **Don't Do This:** * Overuse signals for managing large chunks of component state. For larger states, "useStore" is typically more appropriate. * **Why:** "useSignal" provides fine-grained reactivity with minimal overhead, perfect for specific values. However, for complex state management, "useStore" is better suited. ### 3.5. Props * **Do This:** * Explicitly define prop types using interfaces. This provides type safety and enhances code maintainability. """typescript import { component$ } from '@builder.io/qwik'; interface MyComponentProps { name: string; age: number; } export const MyComponent = component$((props: MyComponentProps) => { return ( <div> <p>Hello, {props.name}!</p> <p>You are {props.age} years old.</p> </div> ); }); """ * **Don't Do This:** * Avoid using "any" for prop types, as this removes type safety and increases the risk of errors. """typescript import { component$ } from '@builder.io/qwik'; export const MyComponent = component$((props: any) => { // Avoid 'any' return ( <div> <p>Hello, {props.name}!</p> <p>You are {props.age} years old.</p> </div> ); }); """ * **Why:** Explicit prop types improve code clarity and prevent type-related errors, leading to more robust and maintainable components. ## 4. Stylistic Consistency Maintaining stylistic consistency across the codebase improves readability and reduces cognitive load for developers. ### 4.1. Quotes * **Do This:** * Use single quotes (') for JSX attributes and double quotes (") for strings, but choose a single one and adhere to it for the entire project. This is a common convention in JavaScript and HTML. """typescript // Good: Single quotes for attributes, double quotes for strings export const MyComponent = component$(() => { return ( <div className="my-class"> {/*className uses double quotes (but single quotes are also fine if all is consistent)*/} <p>Hello, Qwik!</p> </div> ); }); """ * **Don't Do This:** * Inconsistently use single and double quotes in JSX attributes or strings, leading to visual clutter and reduced readability. """typescript // Bad: Inconsistent quote usage export const MyComponent = component$(() => { return ( <div className='my-class'> {/*className uses single quotes*/} <p>Hello, Qwik!</p> </div> ); }); """ * **Why:** Consistent quote usage improves code readability and reduces visual noise. ### 4.2. Arrow Functions * **Do This:** * Prefer arrow functions for concise function expressions, especially in functional components and callbacks. """typescript // Good: Using arrow functions const handleClick = () => { console.log('Button clicked!'); }; export const MyComponent = component$(() => { return <button onClick$={handleClick}>Click me</button>; }); """ * **Don't Do This:** * Use traditional function declarations when arrow functions would be more concise and readable. """typescript // Bad: Using traditional function declaration function handleClick() { console.log('Button clicked!'); } export const MyComponent = component$(() => { return <button onClick$={handleClick}>Click me</button>; }); """ * **Why:** Arrow functions are more concise and have lexical "this" binding, making them ideal for functional components and callbacks. ### 4.3. Object and Array Literals * **Do This:** * Use concise syntax for object and array literals. This improves readability and reduces boilerplate code. """typescript // Good: Concise object and array literals const user = { name: 'John', age: 30 }; const numbers = [1, 2, 3, 4, 5]; """ * **Don't Do This:** * Use verbose or outdated syntax for object and array literals. """typescript // Bad: Verbose object and array literals const user = new Object(); user.name = 'John'; user.age = 30; const numbers = new Array(); numbers[0] = 1; numbers[1] = 2; """ * **Why:** Concise syntax for object and array literals makes the code more readable and maintainable. ### 4.4. Ternary Operators * **Do This:** * Use ternary operators for simple conditional expressions. This makes the code more concise and readable. """typescript // Good: Using ternary operator const isLoggedIn = true; const message = isLoggedIn ? 'Welcome!' : 'Please log in.'; """ * **Don't Do This:** * Use ternary operators for complex conditional logic, as this can reduce readability. """typescript // Bad: Complex ternary operator const isLoggedIn = true; const userRole = 'admin'; const message = isLoggedIn ? userRole === 'admin' ? 'Welcome, Admin!' : 'Welcome, User!' : 'Please log in.'; """ * **Why:** Ternary operators provide a concise way to express simple conditional logic. For more complex logic, use "if" statements for better readability. ## 5. Error Handling Effective error handling is crucial for building robust and maintainable applications. ### 5.1. Try-Catch Blocks * **Do This:** * Use "try-catch" blocks to handle potential errors that might occur during runtime, especially when dealing with asynchronous operations or external APIs. """typescript // Good: Using try-catch block async function fetchData() { try { const response = await fetch('https://example.com/api/data'); const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); return null; } } """ * **Don't Do This:** * Ignore potential errors without handling them, as this can lead to unexpected behavior and difficult debugging. """typescript // Bad: Ignoring potential errors async function fetchData() { const response = await fetch('https://example.com/api/data'); const data = await response.json(); // If fetch call fails, exception will halt execution return data; } """ * **Why:** "try-catch" blocks allow you to gracefully handle errors, preventing the application from crashing and providing informative error messages. ### 5.2. Error Boundaries * **Do This:** * Implement error boundaries in your Qwik components to catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of crashing the component. """typescript // Good: Error Boundary Component import { component$, ErrorBoundary } from '@builder.io/qwik'; interface Props { children: JSX.Element; } export const ErrorBoundaryComponent = component$((props: Props) => { return ( <ErrorBoundary> {props.children} <template q:fallback> <div> <h2>Something went wrong.</h2> <p>Please try again later.</p> </div> </template> </ErrorBoundary> ); }); """ * **Don't Do This:** * Rely solely on global error handlers, as they might not provide sufficient context and can make it difficult to isolate the source of the error. Avoid letting errors crash the entire application. * **Why:** Error boundaries ensure that errors in one part of the application do not affect other parts, improving the overall user experience and maintainability. ### 5.3 Handling Promises * **Do This:** * Always handle promise rejections, either with ".catch()" or by using "async/await" within a "try...catch" block. Unhandled promise rejections can lead to unhandled exceptions and unexpected behavior. """typescript // Good: Handling promise rejections with .catch() fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error fetching data:', error)); // Good: Handling promise rejections with async/await and try...catch async function fetchData() { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); console.log(data); } catch (error) { console.error('Error fetching data:', error); } } """ * **Don't Do This:** * Ignore promise rejections. This can mask errors and lead to unexpected behavior in your application. """typescript // Bad: Ignoring promise rejections fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)); // What if the fetch fails? """ * **Why:** Proper promise rejection handling ensures that errors are caught and handled, leading to a more stable and reliable application. ## 6. Comments and Documentation Clear and concise comments are essential for understanding the purpose and functionality of the code. ### 6.1. Code Comments * **Do This:** * Add comments to explain complex or non-obvious logic. Comments should provide context, explain the purpose of the code, and guide the reader through the implementation. """typescript // Good: Code comments /** * Calculates the discount amount based on the user's membership level. * @param price The original price of the item. * @param membershipLevel The user's membership level (e.g., 'Basic', 'Premium', 'Gold'). * @returns The discount amount. */ function calculateDiscount(price: number, membershipLevel: string): number { let discountPercentage = 0; switch (membershipLevel) { case 'Premium': discountPercentage = 0.1; // 10% discount for Premium members break; case 'Gold': discountPercentage = 0.2; // 20% discount for Gold members break; default: discountPercentage = 0; // No discount for other membership levels } return price * discountPercentage; } """ * **Don't Do This:** * Add redundant comments that simply restate what the code already does. Avoid also using comments to document obvious code. """typescript // Bad: Redundant comments const x = 5; // Assign 5 to x => This isn't descriptive, this is only restating! """ * **Why:** Comments should provide valuable information that is not immediately apparent from the code itself. They should explain the *why* and not just the *what*. ### 6.2. JSDoc Comments * **Do This:** * Use JSDoc-style comments at the beginning of functions and components to describe parameters, return values, and any side effects. This allows IDEs and documentation generators to provide better assistance and documentation. """typescript /** * Fetches user data from the API. * @param id The ID of the user to fetch. * @returns A promise that resolves with the user data, or rejects with an error. */ async function fetchUserData(id:number): Promise<any> { //... } """ ### 6.3. Component Documentation * **Do This:** * Include a comment block at the top of each component file that describes the purpose of the component, its props, and any other relevant information. """typescript // Good: Component documentation /** * A component that displays a user's profile. * * @param props The props for the component. * @param props.name The name of the user. * @param props.age The age of the user. */ import { component$ } from '@builder.io/qwik'; interface MyComponentProps { name: string; age: number; } export const MyComponent = component$((props: MyComponentProps) => { return ( <div> <p>Hello, {props.name}!</p> <p>You are {props.age} years old.</p> </div> ); }); """ * **Don't Do This:** * Omit component documentation, as this can make it difficult for other developers to understand the purpose and usage of the component. * **Why:** Clear component documentation facilitates code reuse and collaboration, helping other developers quickly understand how to use the component and what to expect from it. ## 7. Imports and Exports Properly managing imports and exports is critical for maintaining a clear and modular codebase. ### 7.1. Absolute Imports * **Do This:** * Favor absolute imports over relative imports, especially for modules in deeply nested directories. Absolute imports make it easier to refactor code and move files without breaking import paths. """typescript // Good: Absolute import import { MyComponent } from 'src/components/my-component'; """ * **Don't Do This:** * Use relative imports that rely on the file's location within the directory structure, as this can make the code more brittle and difficult to refactor. """typescript // Bad: Relative import import { MyComponent } from '../../components/my-component'; """ * **Why:** Absolute imports provide a more stable and predictable way to reference modules, reducing the risk of broken imports during refactoring. They also improve code readability by making it clear where modules are located within the project structure. ### 7.2. Grouping Imports * **Do This:** * Group imports from the same module together and separate them from imports from other modules. This improves code readability and makes it easier to identify dependencies. """typescript // Good: Grouped imports // Qwik imports import { component$, useStore } from '@builder.io/qwik'; // Component imports import { Button } from './button'; import { Input } from './input'; // Utility imports import { formatData } from 'src/utils/format'; import { validateForm } from 'src/utils/validation'; """ * **Don't Do This:** * Scatter imports throughout the file or mix them haphazardly, as this can make it more difficult to understand the dependencies of the code. """typescript // Bad: Scattered imports import { Button } from './button'; import { component$ } from '@builder.io/qwik'; import { formatData } from 'src/utils/format'; import { useStore } from '@builder.io/qwik'; import { Input } from './input'; import { validateForm } from 'src/utils/validation'; """ * **Why:** Grouping imports improves code readability and makes it easier to identify the dependencies of the code at a glance. ### 7.3. Named Exports vs. Default Exports * **Do This:** * Primarily use named exports. They are more explicit and prevent naming collisions in larger projects. When the module is the component itself, the name should match the component name and the filename. """typescript // Good: Named export export const MyComponent = component$(() => { // ... }); """ * **Don't Do This:** * Avoid default exports unless there is one primary export from the module (e.g., a single utility function). It’s harder to trace where default exports originate. """typescript // Bad: Default export export default component$(() => { // ... }); """ * **Why:** Named exports make it clear what is being exported from a module, reducing ambiguity and potential naming conflicts. These code style and convention standards are designed to help Qwik developers write code that is consistent, readable, and maintainable. By following these guidelines, development teams can ensure that their codebases are easy to understand, debug, and extend, while also leveraging Qwik's unique features for optimal performance.
# API Integration Standards for Qwik This document outlines the coding standards for API integration within Qwik applications. It aims to provide clear guidelines for connecting to backend services and external APIs in an efficient, maintainable, and secure manner. These guidelines are specifically tailored for Qwik's unique architecture and focus on leveraging its features for optimal performance. ## 1. Architectural Patterns for API Integration Choosing the right architectural pattern is crucial for managing API interactions in a Qwik application. This choice impacts everything from maintainability to performance. ### 1.1. Server-Side Rendering (SSR) with API Proxy **Do This:** Implement an API proxy on your server to handle requests from the client, especially for sensitive data or operations. **Don't Do This:** Directly expose backend API endpoints to the client. **Why:** * **Security:** Protects API keys and backend infrastructure from direct client access. * **CORS Management:** Simplifies Cross-Origin Resource Sharing (CORS) configuration. The proxy can handle CORS headers instead of relying on the backend directly. * **Simplified API Consumption:** Transforms and aggregates data from multiple backend services into a single, streamlined API for the Qwik frontend. This reduces the complexity of the client-side code and improves maintainability. * **Caching:** Implements caching mechanisms at the proxy level to improve performance and reduce load on backend systems. """typescript // /src/routes/proxy/[...proxy].ts (Qwik City route) import { RequestHandler } from '@builder.io/qwik-city'; interface Env { API_BASE_URL: string; } export const onRequest: RequestHandler = async (event) => { const { request, params, env } = event; const apiBaseUrl = (env as Env).API_BASE_URL; // Access env vars properly if (!apiBaseUrl) { return event.json(500, { message: 'API_BASE_URL not configured' }); } const apiUrl = "${apiBaseUrl}/${params.proxy?.join('/')}"; // Construct the backend URL try { const apiResponse = await fetch(apiUrl, { method: request.method, headers: request.headers, body: request.body, }); if (!apiResponse.ok) { console.error('Error from backend:', apiResponse.status, apiResponse.statusText); return event.json(apiResponse.status, { message: "Backend error: ${apiResponse.statusText}" }); } // Forward the response from the backend const data = await apiResponse.json(); event.json(apiResponse.status, data); } catch (error: any) { console.error('Proxy error:', error); return event.json(500, { message: "Proxy error: ${error.message}" }); } }; """ **Explanation:** * This example uses Qwik City's "onRequest" handler to create a proxy route. * The "API_BASE_URL" should be set as an environment variable. **Important:** Access environment variables through "event.env" in Qwik City routes. * It forwards all requests (method, headers, body) to the backend API. * Error handling is crucial both for the "fetch" call and potential backend errors. Returning appropriate HTTP status codes and messages provides better debugging info. ### 1.2. Using "useEndpoint" for Server Functions **Do This:** Employ Qwik's "useEndpoint" for handling server-side logic in a type-safe way. **Don't Do This:** Make direct API calls from components in the browser that bypass server validation or authentication checks. **Why:** * **Type Safety:** "useEndpoint" provides end-to-end type safety from the client to the server, reducing runtime errors. * **Security:** Server functions are executed on the server, preventing client-side manipulation of critical logic or sensitive data. * **Qwik Optimization:** "useEndpoint" is designed to work seamlessly with Qwik's resumability, optimizing for performance. """typescript // src/components/MyForm.tsx import { component$, useStore, $, useContext, useTask } from '@builder.io/qwik'; import { useEndpoint, Form, routeAction$ } from '@builder.io/qwik-city'; interface FormData { name: string; email: string; } export const MyForm = component$(() => { const endpoint = useEndpoint<FormData>('/api/submit-form'); const store = useStore({ name: '', email: '', message: '' }); const submitForm = routeAction$(async (data: FormData, { fail, redirect }) => { try { const response = await fetch('/api/submit-form', { // Use relative URL for the endpoint method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }); if (!response.ok) { console.error('Server error:', response.status, response.statusText); return fail(response.status, 'Failed to submit form'); } redirect('/success'); } catch (e: any) { console.error('API Error', e); return fail(500, 'Form Submission Failed.'); } }); return ( <Form action={submitForm}> <label htmlFor="name">Name:</label> <input type="text" id="name" name="name" value={store.name} onChange$={(e) => (store.name = e.target.value)} /> <label htmlFor="email">Email:</label> <input type="email" id="email" name="email" value={store.email} onChange$={(e) => (store.email = e.target.value)} /> <button type="submit">Submit</button> </Form> ); }); """ """typescript // src/routes/api/submit-form/index.ts (API Endpoint) import { RequestHandler } from '@builder.io/qwik-city'; interface FormData { name: string; email: string; } export const onRequest: RequestHandler = async ({ request, json }) => { try { const data: FormData = await request.json(); if (!data.name || !data.email) { return json(400, { message: 'Name and email are required' }); } // Simulate API call console.log('Received form data:', data); return json(200, { message: 'Form submitted successfully' }); } catch (e) { console.error("Error processing form submission", e); return json(500, {message: "Internal server error."}); } }; """ **Explanation:** * The "MyForm" component uses a "routeAction$" to handle form submission and validation. On submission it POST's the form data to the "/api/submit-form" endpoint * The API Enpoint is defined in "src/routes/api/submit-form/index.ts". This is the actual route that will process the post request and is decoupled from the UI component. * This "routeAction$" ensures the POST happens using the endpoint you define. * Make sure your endpoint handles the data. ### 1.3. Edge Functions for Globally Distributed APIs **Do This:** Utilize edge functions (e.g., Cloudflare Workers, Netlify Functions) for geographically distributed APIs or computationally intensive tasks. **Don't Do This:** Rely solely on a centralized server when dealing with users distributed globally. **Why:** * **Low Latency:** Edge functions are executed closer to the user, reducing latency and improving the user experience. * **Scalability:** Edge functions can automatically scale to handle increased traffic. * **Reduced Load on Origin Server:** Offloads tasks such as image optimization, authentication, and A/B testing to the edge. """typescript // cloudflare/workers/my-edge-function.ts (Example Cloudflare Worker) export interface Env { // Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/ // MY_KV_NAMESPACE: KVNamespace; // // Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/ // MY_DURABLE_OBJECT: DurableObjectNamespace; // // Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/ // MY_BUCKET: R2Bucket; // // Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/ // MY_SERVICE: Fetcher; API_BASE_URL: string; } export default { async fetch( request: Request, env: Env, ctx: ExecutionContext ): Promise<Response> { const apiUrl = "${env.API_BASE_URL}/data"; // Access API URL from environment variable try { const apiResponse = await fetch(apiUrl, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (!apiResponse.ok) { return new Response("API Error: ${apiResponse.status} ${apiResponse.statusText}", { status: apiResponse.status }); } const data = await apiResponse.json(); return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' }, }); } catch (error: any) { console.error('Edge Function Error:', error); return new Response("Edge Function Error: ${error.message}", { status: 500 }); } }, }; """ **Explanation:** * This example demonstrates a simple Cloudflare Worker that fetches data from a backend API. * The "API_BASE_URL" is configured as an environment variable within the Cloudflare Worker's settings. * Error handling is crucial; the worker returns appropriate error messages and status codes. ## 2. Data Fetching Techniques Efficient data fetching is paramount in Qwik applications. Qwik's resumability features can be enhanced with proper data fetching strategies. ### 2.1. Leveraging "useClientEffect$" for Client-Side Fetching **Do This:** Use "useClientEffect$" when you need to fetch data on the client-side after hydration. This fetches the data once the component is active. **Don't Do This:** Fetch data synchronously during component initialization. **Why:** * **Performance:** Prevents blocking the initial rendering of the page. Data fetching happens in the background after the UI is interactive according to the end users needs * **Resumability:** "useClientEffect$" is designed to integrate with Qwik's resumability model. All fetching is driven by the client once the app is active - which speeds up development. """typescript // src/components/DataDisplay.tsx import { component$, useClientEffect$, useState } from '@builder.io/qwik'; interface DataItem { id: number; name: string; } export const DataDisplay = component$(() => { const [data, setData] = useState<DataItem[]>([]); const [loading, setLoading] = useState(false); // Add a loading state const [error, setError] = useState<string | null>(null); useClientEffect$(() => { setLoading(true); // Set loading to true when data fetching starts fetch('/api/data') .then(response => { if (!response.ok) { throw new Error("HTTP error! Status: ${response.status}"); } return response.json(); }) .then(data => { setData(data); }) .catch(e => { setError(e.message); }) .finally(() => { setLoading(false); // Set loading to false when fetching is complete }); }); return ( <> {loading && <p>Loading...</p>} {error && <p>Error: {error}</p>} <ul> {data.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </> ); }); """ **Explanation:** * "useClientEffect$" ensures that the "fetch" call is executed after the component is mounted on the client. * It includes error handling and a "loading" state for a better user experience. ### 2.2. Serializing Data with "server$" for Initial Render **Do This:** Use "server$" to pre-fetch and serialize data on the server during initial rendering, and then pass this serialized data to your Qwik components. **Don't Do This:** Fetch the same data redundantly on both the server and the client. **Why:** * **SEO Optimization:** Provides fully rendered content to search engine crawlers. * **Performance:** Reduces client-side loading time by providing pre-rendered content. Note: pre-rendering will trigger the API call every single time that route is hit. * **Resumability:** Qwik only needs to serialize the data and send it to the client. * **Accessibility:** A fully rendered page is immediately available to users, improving accessibility. """typescript // src/routes/my-page/index.tsx import { component$, useStore, server$, useTask$ } from '@builder.io/qwik'; import type { RequestHandler } from '@builder.io/qwik-city'; interface DataItem { id: number; name: string; } interface MyPageProps { initialData: DataItem[]; } export const useMyPageData = server$(async (): Promise<DataItem[]> => { // Simulate fetching data from an API const response = await fetch('https://jsonplaceholder.typicode.com/users'); if (!response.ok) { throw new Error("HTTP error! Status: ${response.status}"); } const data = await response.json(); // Transform the data to match the DataItem interface const transformedData = data.map((item: any) => ({ id: item.id, name: item.name, })); return transformedData; }); export const MyPage = component$((props: MyPageProps) => { const store = useStore({ data: props.initialData, }); return ( <> <h1>My Page</h1> <ul> {store.data.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </> ); }); // Qwik City Route Definition export const onRequest: RequestHandler = async ({ params, render, }) => { const initialData = await useMyPageData(); // Fetch data on the server const { html, } = await render({ base: '/', routes: [], symbols: [], prefetchStrategy: { progressive: false }, component: () => <MyPage initialData={initialData}/> }); return new Response(html(), { headers: { 'Content-Type': 'text/html; charset=utf-8' }, }); }; """ **Explanation:** * This example uses the "server$" function to fetch the data from a remote source. * It then assigns the data to static props on the server before the render, ensuring that the data is used to SSR the page. ### 2.3. Caching API Responses **Do This:** Implement caching mechanisms to reduce the number of API calls, especially for frequently accessed data. **Don't Do This:** Cache sensitive data without proper security considerations. **Why:** * **Performance:** Reduces latency and improves the responsiveness of the application. * **Cost Savings:** Reduces load on backend systems and lowers API usage costs. * **Offline Support:** Allows the application to function (to some extent) even when the user is offline. """typescript // Service worker (service-worker.ts) const CACHE_NAME = 'my-app-cache-v1'; const urlsToCache = [ '/', '/styles.css', '/script.js', ]; self.addEventListener('install', (event: any) => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => { console.log('Opened cache'); return cache.addAll(urlsToCache); }) ); }); self.addEventListener('fetch', (event: any) => { event.respondWith( caches.match(event.request) .then(response => { if (response) { return response; // Return cached response } // If not cached, fetch from network return fetch(event.request).then( (response) => { // Check if we received a valid response if(!response || response.status !== 200 || response.type !== 'basic') { return response; } // IMPORTANT: Clone the response. A response is a stream // and because we want to use it twice we need to clone it. const responseToCache = response.clone(); caches.open(CACHE_NAME) .then((cache) => { cache.put(event.request, responseToCache); }); return response; } ); } ) ); }); """ **Explanation:** * This example demonstrates a basic service worker that caches API responses. * It intercepts "fetch" requests and checks if the requested resource is available in the cache. * If the resource is not cached, it fetches it from the network and caches the response. **Important Considerations for Caching:** * **Cache Invalidation:** Implement strategies for invalidating the cache when data changes. This could involve using cache busting techniques, time-based expiration, or webhooks. * **Security:** Be careful about caching sensitive data. Use appropriate encryption and access control mechanisms. * **Storage Limits:** Be aware of storage limits imposed by browsers and other caching mechanisms. ## 3. Error Handling and Resilience Robust error handling and resilience are crucial for building reliable Qwik applications. ### 3.1. Centralized Error Handling **Do This:** Implement a centralized error handling mechanism to catch and log errors from API calls. **Don't Do This:** Leave errors unhandled or handle them inconsistently throughout the application. **Why:** * **Maintainability:** Provides a single place to handle errors, making it easier to debug and maintain the application. * **User Experience:** Prevents the application from crashing and provides informative error messages to the user. * **Monitoring:** Allows you to track errors and identify potential problems. """typescript // src/utils/api.ts async function fetchData(url: string, options?: RequestInit) { try { const response = await fetch(url, options); if (!response.ok) { // Log the error to a monitoring service (e.g., Sentry) console.error("API Error: ${response.status} ${response.statusText} - URL: ${url}"); throw new Error("API Error: ${response.status} ${response.statusText}"); } return await response.json(); } catch (error: any) { // Handle the error globally console.error('Global API Error Handler:', error); throw error; // Re-throw the error to be handled by the component } } export { fetchData }; """ """typescript // src/components/MyComponent.tsx import { component$, useClientEffect$, useState } from '@builder.io/qwik'; import { fetchData } from '../utils/api'; interface DataItem { id: number; name: string; } export const MyComponent = component$(() => { const [data, setData] = useState<DataItem[]>([]); const [error, setError] = useState<string | null>(null); useClientEffect$(() => { fetchData('/api/data') .then(data => { setData(data); }) .catch(e => { setError(e.message); // Set the error message in the component's state }); }); return ( <> {error && <p>Error: {error}</p>} <ul> {data.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </> ); }); """ **Explanation:** * The "fetchData"utility function centralizes error handling for all API calls. * It logs errors to the console (or a monitoring service) and re-throws them so components can display informative messages to the user. * The component catches the error and displays an error message. ### 3.2. Retry Mechanisms **Do This:** Implement retry mechanisms with exponential backoff for transient API errors (e.g., network connectivity issues). **Don't Do This:** Retry indefinitely without any backoff strategy as this can overload the API. **Why:** * **Resilience:** Increases the resilience of the application to temporary network issues or server downtime. * **User Experience:** Reduces the likelihood of errors being displayed to the user. """typescript // src/utils/api.ts async function fetchDataWithRetry(url: string, options?: RequestInit, maxRetries = 3, backoffDelay = 1000) { let retryCount = 0; while (retryCount < maxRetries) { try { const response = await fetch(url, options); if (!response.ok) { if (response.status === 429) { // Handle the API response that indicates rate limiting console.warn('Rate limited. Waiting before retrying...'); await delay(backoffDelay * (2 ** retryCount)); // wait before retrying retryCount++; continue; // Continue to the next retry } console.error("API Error: ${response.status} ${response.statusText} - URL: ${url}"); throw new Error("API Error: ${response.status} ${response.statusText}"); } return await response.json(); } catch (error: any) { console.error("Attempt ${retryCount + 1} failed:", error); retryCount++; if (retryCount >= maxRetries) { console.error('Max retries reached. Failing request.'); throw error; // Re-throw the error to be handled by the component } // Wait before retrying with exponential backoff await delay(backoffDelay * (2 ** retryCount)); } } throw new Error('Max retries reached.'); // If the loop completes without returning } function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } export { fetchDataWithRetry }; """ **Explanation:** * The "fetchDataWithRetry" function implements a retry mechanism with exponential backoff. * It retries the API call up to "maxRetries" times. * The "backoffDelay" increases exponentially with each retry. ### 3.3. Circuit Breaker Pattern **Do This:** Implement a circuit breaker pattern to prevent cascading failures when a backend service is unavailable. **Don't Do This:** Continuously call a failing service without giving it a chance to recover. **Why:** * **Stability:** Prevents the application from being overwhelmed by failures in backend services. * **Resilience:** Allows backend services to recover without being bombarded by requests. """typescript // Example simplified circuit breaker in memory with an object: const circuitBreakers: { [key: string]: { state: 'open' | 'closed' | 'half-open', failureCount: number, lastFailure: number } } = {}; const MAX_FAILURES = 5; // Max failures before opening the circuit const RESET_TIMEOUT = 30000; // 30 seconds before attempting a reset async function fetchDataWithCircuitBreaker(url: string, options?: RequestInit) { if (!circuitBreakers[url]) { circuitBreakers[url] = { state: 'closed', failureCount: 0, lastFailure: 0 }; } const circuit = circuitBreakers[url]; // Check the circuit state if (circuit.state === 'open') { if (Date.now() - circuit.lastFailure < RESET_TIMEOUT) { throw new Error('Service unavailable (circuit open)'); } else { circuit.state = 'half-open'; // Attempt a reset } } try { const response = await fetch(url, options); if (!response.ok) { throw new Error("API Error: ${response.status} ${response.statusText}"); } const data = await response.json(); circuit.state = 'closed'; // Reset the circuit if successful circuit.failureCount = 0; return data; } catch (error: any) { circuit.failureCount++; circuit.lastFailure = Date.now(); if (circuit.failureCount >= MAX_FAILURES) { circuit.state = 'open'; // Open the circuit console.warn("Circuit opened for ${url}"); } throw error; } } // Example usage: async function getData() { try { const data = await fetchDataWithCircuitBreaker('/api/data'); console.log('Data:', data); } catch (error: any) { console.error('Error fetching data:', error.message); } } """ **Explanation:** * A simplified circuit breaker is implemented using an object "circuitBreakers". This should be replaced with a more robust solution for real-world applications (e.g., using a library or a dedicated service). * The circuit breaker has three states: "open", "closed", and "half-open". * In the "closed" state, requests are allowed through. If a request fails, the "failureCount" is incremented. If the "failureCount" exceeds "MAX_FAILURES", the circuit opens. * If the service hasn't been failing for the period as defined by "RESET_TIMEOUT" the circuit enters the "half-open" state, one request is allowed through to test the service. If the request is successful, the circuit closes. If it fails, the circuit opens again * When the service starts failing, it will enter the "open state" where no requests are allowed through. * When the circuit is "open", requests are immediately rejected, preventing further calls to the failing service. After a timeout period, the circuit enters the "half-open" state, allowing a limited number of requests to test the service. If the service recovers, the circuit closes. * While you could use this example, it is important to use a robust solution for real-world applications for managing a circuit breaker. ## 4. Security Considerations API integration introduces several security considerations. Following these principles is important to protect the application. ### 4.1. Input Validation **Do This:** Validate all input from the client before sending it to the backend API or storing it in the database. **Don't Do This:** Trust client-side data without validation. **Why:** * **Prevents Injection Attacks:** Protects against SQL injection, XSS, and other injection attacks. * **Data Integrity:** Ensures that data is consistent and accurate. * **Application Stability:** Prevents unexpected errors caused by invalid data. """typescript // src/routes/api/submit-form/index.ts import { RequestHandler } from '@builder.io/qwik-city'; interface FormData { name: string; email: string; message: string; } function isValidEmail(email: string): boolean { // Basic email validation regex (improve as needed) const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } export const onRequest: RequestHandler = async ({ request, json }) => { try { const data: FormData = await request.json(); // Validate input if (!data.name || data.name.length < 2) { return json(400, { message: 'Name must be at least 2 characters long' }); } if (!data.email || !isValidEmail(data.email)) { return json(400, { message: 'Invalid email address' }); } if (!data.message || data.message.length < 10) { return json(400, { message: 'Message must be at least 10 characters long' }); } // Simulate API call console.log('Received form data:', data); return json(200, { message: 'Form submitted successfully' }); } catch (e) { console.error("Error processing form submission", e); return json(500, {message: "Internal server error."}); } }; """ **Explanation:** * This example validates the "name", "email", and "message" fields from the form data. * It uses a regex to validate the email address. ### 4.2. Secure Authentication and Authorization **Do This:** Implement secure authentication and authorization mechanisms to protect API endpoints. **Don't Do This:** Rely on client-side authentication or authorization. **Why:** * **Data Protection:** Ensures that only authorized users can access sensitive data. * **System Integrity:** Protects against unauthorized modification or deletion of data. * **Compliance:** Meets regulatory requirements for data security. **Techniques:** * **JWT (JSON Web Tokens):** Use JWTs for authentication and authorization. Generate JWTs on the server after successful authentication. * **OAuth 2.0:** Implement OAuth 2.0 for authentication and authorization, especially when integrating with third-party services. * **Role-Based Access Control (RBAC):** Implement RBAC to restrict access to API endpoints based on user roles. """typescript // Example JWT Authentication (Simplified) import { sign, verify } from 'jsonwebtoken'; const JWT_SECRET = 'your-secret-key'; // Store this securely using env vars function generateToken(payload: any): string { return sign(payload, JWT_SECRET, { expiresIn: '1h' }); } function verifyToken(token: string): any { try { return verify(token, JWT_SECRET); } catch (error) { return null; } } // Example Route using JWT Authentication import { RequestHandler } from '@builder.io/qwik-city'; export const onRequestProtected: RequestHandler = async ({ request, json, headers }) => { const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return json(401, { message: 'Unauthorized: Missing or invalid token' }); } const token = authHeader.substring(7); // Remove 'Bearer ' const user = verifyToken(token); if (!user) { return json(401, { message: 'Unauthorized: Invalid token' }); } // Access user data from the verified token console.log('Authenticated user:', user); // ... your protected API logic here ... return json(200, { message: 'Protected resource accessed successfully' }); }; """ **Explanation:** * This is a *simplified* example. Use established libraries and patterns for JWT authentication in a production environment. * It generates a JWT token upon successful authentication. * The "verifyToken" function verifies the JWT token. * API routes that require authentication should check for a valid JWT token in the "Authorization" header. ### 4.3. Rate Limiting **Do This:** Implement rate limiting to prevent abuse and protect API endpoints from denial-of-service attacks. **Don't Do This:** Allow unlimited requests to API endpoints. **Why:** * **System Protection:** Protects against malicious attacks or unintentional overuse of API resources. * **Fair Usage:** Ensures that all users have fair access to API resources. * **Cost Management:** Reduces API usage costs. This is a critical pattern when developing any modern Qwik application. Please keep the above in mind when developing!