10 Prompts for Generating JavaScript and TypeScript Code: From React Components to Node.js Utilities

Introduction

In 2026, generative AI has become a standard tool in the daily workflow of JavaScript and TypeScript developers. According to a 2025 Stack Overflow survey, over 60% of professional developers now use AI assistants to generate code, with JavaScript and TypeScript being the top languages for prompt-based generation. However, the difference between a useful AI-generated code snippet and a bug-filled mess often lies in the quality of the prompt.

This article is a curated collection of 10 battle-tested prompts for generating JS/TS code. Each prompt is designed to solve a specific real-world problem: building React components, writing Node.js middleware, crafting utility functions, and more. You’ll get the exact prompt text, a realistic example of AI output, and a breakdown of why it works.

Whether you’re a junior developer looking to speed up boilerplate creation or a senior architect exploring AI-assisted scaffolding, these prompts will help you get consistent, production-ready results.

1. React Functional Component with TypeScript Props and JSDoc

Why this prompt? React components are the most common code generation task. A good prompt must specify the component’s purpose, props interface, and documentation standards.

Prompt:

Generate a React functional component in TypeScript. The component is a 'UserCard' that displays a user's avatar, name, and email. The props interface should include:
- id: string
- name: string
- email: string
- avatarUrl?: string (optional)
- onDelete: (id: string) => void

Include JSDoc comments for the component and each prop. Use React.FC for typing. Style with inline Tailwind CSS classes (e.g., 'rounded-full', 'p-4').

Example AI-generated result:

import React from 'react';

/**
 * UserCard component displays user information with a delete action.
 * @param props - Component props
 * @param props.id - Unique identifier for the user
 * @param props.name - Full name of the user
 * @param props.email - Email address of the user
 * @param props.avatarUrl - Optional URL for the user's avatar image
 * @param props.onDelete - Callback fired when delete button is clicked (receives user id)
 */
interface UserCardProps {
  id: string;
  name: string;
  email: string;
  avatarUrl?: string;
  onDelete: (id: string) => void;
}

const UserCard: React.FC<UserCardProps> = ({
  id,
  name,
  email,
  avatarUrl,
  onDelete
}) => {
  return (
    <div className="flex items-center p-4 border rounded-lg shadow-sm">
      <img
        src={avatarUrl || '/default-avatar.png'}
        alt={`${name}'s avatar`}
        className="w-12 h-12 rounded-full mr-4"
      />
      <div className="flex-1">
        <h3 className="text-lg font-semibold">{name}</h3>
        <p className="text-gray-600">{email}</p>
      </div>
      <button
        onClick={() => onDelete(id)}
        className="px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600"
      >
        Delete
      </button>
    </div>
  );
};

export default UserCard;

Why this works: The prompt explicitly defines the interface, optional props, and styling approach. Including JSDoc instructions ensures the AI adds documentation that tools like VSCode IntelliSense can display.

2. Custom React Hook for API Data Fetching with Loading and Error States

Why this prompt? Data fetching hooks are a staple of modern React apps. A strong prompt must cover loading, error, and success states, plus TypeScript generics.

Prompt:

Create a custom React hook named 'useFetch' in TypeScript. It should accept a URL string and an optional RequestInit object. The hook should return an object with:
- data: T | null (generic type)
- loading: boolean
- error: string | null
- refetch: () => void

Use the native fetch API. Handle race conditions with AbortController. Include cleanup in useEffect. Add a JSDoc comment explaining usage.

Example AI-generated result:

import { useState, useEffect, useCallback } from 'react';

/**
 * useFetch - generic data fetching hook with loading/error states.
 * @param url - API endpoint URL
 * @param options - Optional fetch options (method, headers, etc.)
 * @returns Object containing data, loading, error, and refetch function
 */
function useFetch<T>(url: string, options?: RequestInit) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  const fetchData = useCallback(async () => {
    setLoading(true);
    setError(null);
    const abortController = new AbortController();

    try {
      const response = await fetch(url, {
        ...options,
        signal: abortController.signal
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const result: T = await response.json();
      setData(result);
    } catch (err) {
      if (err instanceof Error) {
        setError(err.message);
      } else {
        setError('An unknown error occurred');
      }
    } finally {
      setLoading(false);
    }

    return () => abortController.abort();
  }, [url, options]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return { data, loading, error, refetch: fetchData };
}

export default useFetch;

Why this works: The prompt explicitly asks for AbortController to prevent memory leaks from race conditions. Including the generic T ensures type safety. The refetch function is a common requirement for manual refresh.

3. Node.js Express Middleware for Request Logging with Timestamps

Why this prompt? Middleware is a core concept in Node.js backends. A good prompt should specify the format, output method, and performance considerations.

Prompt:

Write an Express.js middleware function in TypeScript that logs incoming HTTP requests. For each request, log:
- timestamp (ISO 8601 format)
- HTTP method
- URL path
- response status code
- response time in milliseconds

Use console.log for output. Add type annotations for Request, Response, and NextFunction from 'express'. Ensure the middleware does not block the request-response cycle.

Example AI-generated result:

import { Request, Response, NextFunction } from 'express';

/**
 * requestLogger - Express middleware for logging HTTP requests.
 * Logs timestamp, method, path, status code, and response time.
 */
const requestLogger = (req: Request, res: Response, next: NextFunction): void => {
  const start = Date.now();

  // Hook into the response 'finish' event to capture status code and timing
  res.on('finish', () => {
    const duration = Date.now() - start;
    const logEntry = {
      timestamp: new Date().toISOString(),
      method: req.method,
      path: req.path,
      statusCode: res.statusCode,
      responseTimeMs: duration
    };
    console.log(JSON.stringify(logEntry));
  });

  next();
};

export default requestLogger;

Why this works: The prompt specifies ISO 8601 timestamps and response time measurement. Asking for non-blocking behavior (using the finish event) ensures the middleware doesn't slow down requests. Structured JSON logging is a best practice for machine-parseable logs.

4. TypeScript Utility Function for Deep Object Cloning

Why this prompt? Deep cloning is a common utility need. A good prompt must handle edge cases like circular references, dates, and arrays.

Prompt:

Write a TypeScript utility function 'deepClone' that performs a deep copy of an object. It should handle:
- Nested objects and arrays
- Date objects (clone as Date)
- Map and Set
- Circular references (return a new reference or throw a meaningful error)
- Primitive types (string, number, boolean, null, undefined)

Use recursive approach. Add type annotations with generics. Include JSDoc.

Example AI-generated result:

/**
 * deepClone - Creates a deep clone of a value.
 * Handles objects, arrays, Date, Map, Set, and primitives.
 * Throws an error if a circular reference is detected.
 * @param obj - The value to clone
 * @returns A deep clone of the input
 */
function deepClone<T>(obj: T, seen = new WeakMap<object, any>()): T {
  // Handle primitives and null/undefined
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // Check for circular reference
  if (seen.has(obj)) {
    return seen.get(obj);
  }

  // Handle Date
  if (obj instanceof Date) {
    return new Date(obj.getTime()) as any;
  }

  // Handle Map
  if (obj instanceof Map) {
    const cloneMap = new Map();
    seen.set(obj, cloneMap);
    obj.forEach((value, key) => {
      cloneMap.set(deepClone(key, seen), deepClone(value, seen));
    });
    return cloneMap as any;
  }

  // Handle Set
  if (obj instanceof Set) {
    const cloneSet = new Set();
    seen.set(obj, cloneSet);
    obj.forEach(value => {
      cloneSet.add(deepClone(value, seen));
    });
    return cloneSet as any;
  }

  // Handle arrays and plain objects
  const clone = Array.isArray(obj) ? [] : {};
  seen.set(obj, clone);

  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], seen);
    }
  }

  return clone as any;
}

export default deepClone;

Why this works: The prompt explicitly mentions circular references, which is a classic gotcha. Using WeakMap for tracking visited objects is the standard approach. Including Date, Map, and Set makes the function production-ready.

5. React Form Component with Validation Using React Hook Form and Zod

Why this prompt? Form handling is one of the most repetitive tasks in React. Combining React Hook Form with Zod schema validation is a modern standard.

Prompt:

Generate a React form component in TypeScript for user registration. Fields: name (string, min 2 chars), email (valid email), password (min 8 chars, must contain a number). Use react-hook-form with zod resolver. Display inline error messages. Style with basic Tailwind classes.

Example AI-generated result:

import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const registrationSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email address'),
  password: z
    .string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/\d/, 'Password must contain at least one number')
});

type RegistrationFormData = z.infer<typeof registrationSchema>;

interface RegistrationFormProps {
  onSubmit: (data: RegistrationFormData) => void;
}

const RegistrationForm: React.FC<RegistrationFormProps> = ({ onSubmit }) => {
  const {
    register,
    handleSubmit,
    formState: { errors }
  } = useForm<RegistrationFormData>({
    resolver: zodResolver(registrationSchema)
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="max-w-md mx-auto p-6 space-y-4">
      <div>
        <label htmlFor="name" className="block text-sm font-medium">Name</label>
        <input
          id="name"
          {...register('name')}
          className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
        />
        {errors.name && <p className="text-red-500 text-sm mt-1">{errors.name.message}</p>}
      </div>

      <div>
        <label htmlFor="email" className="block text-sm font-medium">Email</label>
        <input
          id="email"
          type="email"
          {...register('email')}
          className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
        />
        {errors.email && <p className="text-red-500 text-sm mt-1">{errors.email.message}</p>}
      </div>

      <div>
        <label htmlFor="password" className="block text-sm font-medium">Password</label>
        <input
          id="password"
          type="password"
          {...register('password')}
          className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
        />
        {errors.password && <p className="text-red-500 text-sm mt-1">{errors.password.message}</p>}
      </div>

      <button
        type="submit"
        className="w-full py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
      >
        Register
      </button>
    </form>
  );
};

export default RegistrationForm;

Why this works: The prompt specifies the validation rules (min length, regex) and the libraries to use. The AI generates the schema and form correctly, including error rendering. This saves 15-20 minutes of boilerplate coding.

6. TypeScript Generic Function for Array Grouping by Key

Why this prompt? Array grouping is a frequent data transformation task. A generic solution is reusable across projects.

Prompt:

Write a TypeScript function 'groupBy' that takes an array of objects and a key (string or function) and returns an object where keys are the group identifiers and values are arrays of matching objects. The function should be generic over the array element type. Include JSDoc and an example usage.

Example AI-generated result:

/**
 * groupBy - Groups an array of objects by a specified key or accessor function.
 * @param array - The array to group
 * @param key - A string key (property name) or a function that returns the group key
 * @returns An object with group keys as properties and arrays as values
 */
function groupBy<T>(array: T[], key: keyof T | ((item: T) => string)): Record<string, T[]> {
  return array.reduce((acc, item) => {
    const groupKey = typeof key === 'function' ? key(item) : String(item[key]);
    if (!acc[groupKey]) {
      acc[groupKey] = [];
    }
    acc[groupKey].push(item);
    return acc;
  }, {} as Record<string, T[]>);
}

// Example usage:
interface User {
  id: number;
  role: string;
}
const users: User[] = [
  { id: 1, role: 'admin' },
  { id: 2, role: 'user' },
  { id: 3, role: 'admin' }
];
const groupedByRole = groupBy(users, 'role');
// Result: { admin: [{ id: 1, role: 'admin' }, { id: 3, role: 'admin' }], user: [{ id: 2, role: 'user' }] }

Why this works: The prompt specifies both a string key and a function accessor, making the function flexible. The generics ensure type safety. The example usage helps the AI understand the expected output format.

7. Node.js File Reading Utility with Async/Await and Error Handling

Why this prompt? File I/O is a common backend task. A good prompt should handle errors gracefully and use modern async patterns.

Prompt:

Create a TypeScript utility function 'readFileSafe' that reads a file from disk using Node.js fs/promises. It should:
- Accept a file path (string)
- Return the file content as a string if successful
- Return null if the file does not exist (ENOENT error) OR if any other error occurs, log a warning and return null
- Use try/catch
- Include JSDoc

Example AI-generated result:

import { readFile } from 'fs/promises';
import { constants } from 'fs';

/**
 * readFileSafe - Reads a file asynchronously and returns its content or null on failure.
 * Returns null for ENOENT (file not found) and any other errors, after logging a warning.
 * @param filePath - Path to the file
 * @returns File content as string, or null
 */
async function readFileSafe(filePath: string): Promise<string | null> {
  try {
    const content = await readFile(filePath, 'utf-8');
    return content;
  } catch (err) {
    if (err instanceof Error) {
      if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
        console.warn(`File not found: ${filePath}`);
      } else {
        console.warn(`Error reading file ${filePath}: ${err.message}`);
      }
    }
    return null;
  }
}

export default readFileSafe;

Why this works: The prompt explicitly mentions fs/promises and the ENOENT error code, which guides the AI to the correct Node.js API. The requirement to log warnings instead of crashing makes it safe for production use.

8. React Context Provider with TypeScript and useReducer

Why this prompt? State management with Context and useReducer is a common pattern for medium-sized apps. A good prompt must define actions, reducer, and provider.

Prompt:

Generate a React context provider in TypeScript for a simple shopping cart. The cart state should be an array of items, where each item has: id (string), name (string), price (number), quantity (number).

Actions:
- ADD_ITEM (payload: Omit<CartItem, 'quantity'>)  adds item or increments quantity if exists
- REMOVE_ITEM (payload: string)  removes item by id
- CLEAR_CART

Create the context, the provider component, and a custom hook 'useCart'. Use useReducer inside the provider. Export all necessary types.

Example AI-generated result:

import React, { createContext, useContext, useReducer, ReactNode } from 'react';

// Types
interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
}

type CartAction =
  | { type: 'ADD_ITEM'; payload: Omit<CartItem, 'quantity'> }
  | { type: 'REMOVE_ITEM'; payload: string }
  | { type: 'CLEAR_CART' };

interface CartState {
  items: CartItem[];
}

// Reducer
function cartReducer(state: CartState, action: CartAction): CartState {
  switch (action.type) {
    case 'ADD_ITEM': {
      const existingIndex = state.items.findIndex(item => item.id === action.payload.id);
      if (existingIndex >= 0) {
        const updatedItems = [...state.items];
        updatedItems[existingIndex] = {
          ...updatedItems[existingIndex],
          quantity: updatedItems[existingIndex].quantity + 1
        };
        return { items: updatedItems };
      }
      return { items: [...state.items, { ...action.payload, quantity: 1 }] };
    }
    case 'REMOVE_ITEM':
      return { items: state.items.filter(item => item.id !== action.payload) };
    case 'CLEAR_CART':
      return { items: [] };
    default:
      return state;
  }
}

// Context
interface CartContextValue {
  state: CartState;
  dispatch: React.Dispatch<CartAction>;
}

const CartContext = createContext<CartContextValue | undefined>(undefined);

// Provider
interface CartProviderProps {
  children: ReactNode;
}

export const CartProvider: React.FC<CartProviderProps> = ({ children }) => {
  const [state, dispatch] = useReducer(cartReducer, { items: [] });

  return (
    <CartContext.Provider value={{ state, dispatch }}>
      {children}
    </CartContext.Provider>
  );
};

// Custom hook
export const useCart = (): CartContextValue => {
  const context = useContext(CartContext);
  if (!context) {
    throw new Error('useCart must be used within a CartProvider');
  }
  return context;
};

export type { CartItem, CartAction, CartState };

Why this works: The prompt defines clear actions and state shape. The AI correctly implements the logic for adding items (incrementing quantity) and removing items. The custom hook with error checking is a best practice.

9. TypeScript Decorator for Logging Method Calls

Why this prompt? Decorators are a powerful metaprogramming feature in TypeScript (and experimental in JavaScript). This prompt targets a practical use case.

Prompt:

Write a TypeScript method decorator named 'LogMethod' that logs the method name, arguments, and return value every time the decorated method is called. The decorator should be usable in a class. Use console.log for output. Include type annotations for the decorator signature. Assume TypeScript's experimentalDecorators is enabled.

Example AI-generated result:

/**
 * LogMethod - Decorator that logs method calls with arguments and return value.
 */
function LogMethod(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
): PropertyDescriptor {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with arguments:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`Method ${propertyKey} returned:`, result);
    return result;
  };

  return descriptor;
}

// Usage example
class Calculator {
  @LogMethod
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(3, 4);
// Console output:
// Calling add with arguments: [3, 4]
// Method add returned: 7

Why this works: The prompt specifies the decorator signature and behavior. The AI correctly implements the wrapper that preserves the original function's context using apply. Including a usage example helps verify correctness.

10. Full CRUD API Endpoint with Express and TypeScript (User Resource)

Why this prompt? CRUD endpoints are the bread and butter of backend development. A comprehensive prompt should cover all HTTP methods, error handling, and validation.

Prompt:

Generate a complete set of Express.js route handlers in TypeScript for a User resource. Include:
- GET /users  returns a list of users (mock data)
- GET /users/:id  returns a single user by ID, 404 if not found
- POST /users  creates a user (validate that name and email are provided, return 400 if missing)
- PUT /users/:id  updates a user (merge with existing, return 404 if not found)
- DELETE /users/:id  deletes a user, return 204

Use an in-memory array as data store. Add proper HTTP status codes. Use express Router. Export the router.

Example AI-generated result:

import { Router, Request, Response } from 'express';

interface User {
  id: number;
  name: string;
  email: string;
}

const router = Router();

// In-memory data store
let users: User[] = [
  { id: 1, name: 'Alice Johnson', email: 'alice@example.com' },
  { id: 2, name: 'Bob Smith', email: 'bob@example.com' }
];
let nextId = 3;

// GET /users
router.get('/', (req: Request, res: Response) => {
  res.json(users);
});

// GET /users/:id
router.get('/:id', (req: Request, res: Response) => {
  const id = parseInt(req.params.id, 10);
  const user = users.find(u => u.id === id);
  if (!user) {
    return res.status(404).json({ message: 'User not found' });
  }
  res.json(user);
});

// POST /users
router.post('/', (req: Request, res: Response) => {
  const { name, email } = req.body;
  if (!name || !email) {
    return res.status(400).json({ message: 'Name and email are required' });
  }
  const newUser: User = { id: nextId++, name, email };
  users.push(newUser);
  res.status(201).json(newUser);
});

// PUT /users/:id
router.put('/:id', (req: Request, res: Response) => {
  const id = parseInt(req.params.id, 10);
  const index = users.findIndex(u => u.id === id);
  if (index === -1) {
    return res.status(404).json({ message: 'User not found' });
  }
  const { name, email } = req.body;
  users[index] = { ...users[index], ...(name && { name }), ...(email && { email }) };
  res.json(users[index]);
});

// DELETE /users/:id
router.delete('/:id', (req: Request, res: Response) => {
  const id = parseInt(req.params.id, 10);
  const index = users.findIndex(u => u.id === id);
  if (index === -1) {
    return res.status(404).json({ message: 'User not found' });
  }
  users.splice(index, 1);
  res.status(204).send();
});

export default router;

Why this works: The prompt explicitly lists all CRUD operations with correct HTTP methods and status codes. The AI implements validation (400 for missing fields) and proper merging for PUT. Using an in-memory array keeps the example simple and testable.

Conclusion

These 10 prompts demonstrate a key principle: the quality of AI-generated code directly mirrors the specificity of your instructions. By including explicit types, edge cases, and expected behavior, you can turn a generic AI assistant into a reliable pair programmer.

To get the most out of these prompts:
- Always specify the language flavor (TypeScript vs JavaScript, React vs Vanilla JS).
- Provide context (which libraries, which patterns).
- Ask for error handling — AI tends to write happy-path code unless prompted otherwise.
- Review and test — AI-generated code is a starting point, not a final product.

Now, open your editor, paste one of these prompts into your favorite AI tool, and see how quickly you can scaffold a production-quality component or utility. Happy coding!

← All posts

Comments