200 OK
What is HTTP 200 OK?
When you ask a website for something - like a picture, a video, or information -...
Explain Like I’m 3
You asked for something and you got it! Everything worked perfectly! It’s like when you ask for your favorite toy and someone brings it to you - you asked, and you got exactly what you wanted!
Example: You say ‘Can I have a cookie?’ and someone gives you a cookie. That’s it working! You got what you asked for.
Explain Like I’m 5
When you ask a website for something - like a picture, a video, or information - and the website finds it and sends it to you, it says ‘200 OK!’ That means everything worked perfectly and you got what you asked for. It’s the most common ‘success’ message on the internet. Almost every time you see a webpage or watch a video online, the website is saying ‘200 OK’ in the background, meaning ‘Here’s what you wanted!’
Example: You search for pictures of puppies on a website. The website finds the puppy pictures and shows them to you. Behind the scenes, the website is saying ‘200 OK - Here are your puppy pictures!’ to your computer.
Jr. Developer
HTTP 200 OK is the standard success response indicating the request was successful. The meaning depends on the HTTP method: GET requests return the requested resource in the response body, POST requests return the result or status of the action performed, PUT/PATCH return the updated resource, DELETE confirms deletion (though 204 is often preferred). 200 responses are cacheable by default. If you have nothing to return in the response body, consider using 204 No Content instead. For resource creation, 201 Created is more semantically correct than 200. The 200 status is the most common HTTP response - every successful API call, web page load, or file download typically uses it.
Example: A user requests their profile data via GET /users/123. Your API fetches the user from the database and returns it with 200 OK. The response includes the user object in JSON format, and the browser can cache this response based on cache headers.
Code Example
// Express.js returning 200 with dataapp.get('/users/:id', async (req, res) => { const user = await db.findUser(req.params.id);
if (!user) { return res.status(404).json({ error: 'User not found' }); }
// 200 is default, but explicit is better res.status(200).json({ id: user.id, name: user.name, email: user.email });});
// Or implicitly (defaults to 200)app.get('/status', (req, res) => { res.json({ status: 'healthy' }); // Sends 200 automatically});Crash Course
200 OK is the standard HTTP success status code, defined in RFC 9110 Section 15.3.1, indicating the request has succeeded. The response payload varies by HTTP method: GET returns the target resource, HEAD returns metadata headers without a body, POST returns action results or status, PUT/PATCH return the updated resource representation, DELETE confirms successful deletion, OPTIONS lists allowed methods, TRACE echoes the request for debugging. Unlike 201 Created (new resource) or 204 No Content (no payload), 200 indicates general success with a response body. 200 responses are cacheable by default - you can control caching with Cache-Control, Expires, and ETag headers. Best practices: use 201 for resource creation, 204 when no body is needed, 202 for async operations, and 200 for everything else. The response should always include a meaningful payload - if there’s nothing to return, 204 is more appropriate. In REST APIs, GET and POST to collection endpoints commonly return 200 with arrays or paginated results.
Example: An e-commerce API search endpoint receives POST /api/products/search with filter criteria. The server queries the database, finds 47 matching products, and returns 200 OK with a JSON array containing product data, pagination metadata, and applied filters. The client receives the results and renders them. The response is cached for 60 seconds to improve performance for identical searches.
Code Example
// Comprehensive 200 usage in REST APIconst express = require('express');const app = express();
app.get('/api/products', async (req, res) => { const { page = 1, limit = 20, category } = req.query;
const products = await db.getProducts({ category, offset: (page - 1) * limit, limit: parseInt(limit) });
const total = await db.countProducts({ category });
// Return 200 with data and pagination metadata res.status(200) .set('Cache-Control', 'public, max-age=60') .json({ data: products, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / limit) } });});
app.put('/api/products/:id', async (req, res) => { const updated = await db.updateProduct( req.params.id, req.body );
// Return 200 with updated resource res.status(200).json(updated);});
app.post('/api/search', async (req, res) => { const results = await searchEngine.query(req.body.query);
// POST that doesn't create resource - returns 200 res.status(200).json({ query: req.body.query, results, timestamp: new Date().toISOString() });});Deep Dive
The 200 OK status code, specified in RFC 9110 Section 15.3.1 (which obsoletes RFC 7231), is the standard response for successful HTTP requests. The semantic meaning and expected payload vary by request method, as defined by the HTTP method specifications. Per RFC 9110, a 200 response is cacheable by default - a cache may store and reuse it without explicit freshness information unless prohibited by Cache-Control directives or method-specific caching rules. The presence and nature of a payload is method-dependent: safe methods like GET and HEAD describe the target resource, while unsafe methods like POST describe the action’s result or status. If an origin server generates a payload body of zero length for a 200 response, it SHOULD send 204 No Content instead, as this more accurately represents the semantic intent.
Technical Details
Caching semantics for 200 responses follow RFC 9111 (HTTP Caching). By default, 200 responses to GET, HEAD, and POST are cacheable, though POST caching is rarely implemented by browsers. Heuristic caching applies when no explicit freshness information exists - caches may use heuristics like (Date - Last-Modified) * 0.1 as a freshness lifetime. Explicit cache control via Cache-Control header overrides defaults: Cache-Control: private prevents shared cache storage, Cache-Control: no-store prevents any caching, Cache-Control: max-age=3600 sets 1-hour freshness. ETags enable conditional requests: the server sends ETag: “abc123” with the 200 response, and clients include If-None-Match: “abc123” in subsequent requests, receiving 304 Not Modified if the resource hasn’t changed.\n\nContent negotiation affects 200 responses when multiple representations exist for a resource. Clients send Accept headers indicating preferred media types, and servers respond with the best match and appropriate Content-Type header. The Vary header indicates which request headers affect the response selection, enabling correct caching: Vary: Accept-Encoding tells caches that gzipped and uncompressed versions are different cache entries.\n\nCompression is commonly applied to 200 responses for bandwidth optimization. When clients send Accept-Encoding: gzip, deflate, br, servers may compress the response body and indicate this with Content-Encoding: gzip. Content-Length should reflect the compressed size. Transfer-Encoding: chunked allows streaming responses of unknown length, useful for dynamically generated content.\n\nRange requests enable partial content delivery, particularly for large files and video streaming. Clients send Range: bytes=0-1023 to request the first 1KB. If the server supports ranges, it responds with 206 Partial Content. If ranges aren’t supported, it returns 200 with the full resource. Servers indicate range support with Accept-Ranges: bytes in responses.\n\nSecurity considerations include information disclosure through overly verbose error details in 200 responses containing error information (anti-pattern). Some APIs incorrectly return 200 with error objects like {“status”: “error”} - this violates HTTP semantics and breaks intermediate caching, proxies, and monitoring tools expecting 4xx/5xx for errors. Cache poisoning attacks exploit incorrect Vary header usage, causing malicious content to be cached and served to multiple users.\n\nPerformance optimization for 200 responses includes CDN edge caching with long TTLs for static resources, browser caching with appropriate max-age for dynamic content, compression reducing payload size by 60-90% for text content, HTTP/2 Server Push preemptively sending 200 responses for anticipated requests (deprecated in Chrome due to cache pollution), and Early Hints (103) allowing browsers to preload resources while waiting for the final 200 response.
Code Example
// Production-grade 200 OK handling with caching, ETags, and compression\nconst express = require('express');\nconst crypto = require('crypto');\nconst compression = require('compression');\nconst app = express();\n\n// Enable compression middleware\napp.use(compression());\n\n// Helper to generate ETags\nfunction generateETag(content) {\n return crypto\n .createHash('md5')\n .update(JSON.stringify(content))\n .digest('hex');\n}\n\napp.get('/api/products/:id', async (req, res) => {\n const product = await db.getProduct(req.params.id);\n \n if (!product) {\n return res.status(404).json({ error: 'Product not found' });\n }\n \n // Generate ETag for conditional requests\n const etag = generateETag(product);\n const clientETag = req.headers['if-none-match'];\n \n // Return 304 Not Modified if ETag matches\n if (clientETag === etag) {\n return res.status(304).end();\n }\n \n // Set comprehensive caching headers\n res.set({\n 'ETag': etag,\n 'Cache-Control': 'public, max-age=300, must-revalidate',\n 'Vary': 'Accept-Encoding',\n 'Last-Modified': product.updated_at.toUTCString(),\n 'Content-Type': 'application/json; charset=utf-8'\n });\n \n // Return 200 with product data\n res.status(200).json(product);\n});\n\n// Paginated collection with proper caching\napp.get('/api/products', async (req, res) => {\n const { page = 1, limit = 20, category } = req.query;\n \n const cacheKey = \`products:${category}:${page}:${limit}\`;\n \n // Check if we have cached results\n const cached = await redis.get(cacheKey);\n if (cached) {\n return res.status(200)\n .set('X-Cache', 'HIT')\n .set('Cache-Control', 'public, max-age=60')\n .json(JSON.parse(cached));\n }\n \n // Query database\n const [products, total] = await Promise.all([\n db.getProducts({ category, offset: (page - 1) * limit, limit }),\n db.countProducts({ category })\n ]);\n \n const response = {\n data: products,\n pagination: {\n page: parseInt(page),\n limit: parseInt(limit),\n total,\n pages: Math.ceil(total / limit),\n hasNext: page * limit < total,\n hasPrev: page > 1\n },\n _links: {\n self: \`/api/products?page=${page}&limit=${limit}\`,\n next: page * limit < total ? \`/api/products?page=${parseInt(page) + 1}&limit=${limit}\` : null,\n prev: page > 1 ? \`/api/products?page=${parseInt(page) - 1}&limit=${limit}\` : null\n }\n };\n \n // Cache for 60 seconds\n await redis.setex(cacheKey, 60, JSON.stringify(response));\n \n res.status(200)\n .set('X-Cache', 'MISS')\n .set('Cache-Control', 'public, max-age=60')\n .set('Vary', 'Accept-Encoding')\n .json(response);\n});\n\n// Search endpoint that returns 200 (doesn't create resource)\napp.post('/api/search', async (req, res) => {\n const { query, filters = {} } = req.body;\n \n const results = await searchEngine.query(query, filters);\n \n // POST responses are cacheable but rarely cached...Frequently Asked Questions
What's the difference between 200 OK and 201 Created?
200 OK is for general success, while 201 Created specifically indicates a new resource was created. Use 201 for POST requests that create resources (along with a Location header pointing to the new resource). Use 200 for GET, PUT, PATCH, DELETE, or POST requests that perform actions without creating new resources (like search).
Should I use 200 or 204 when there's no response body?
Use 204 No Content when there's intentionally no response body (like successful DELETE operations). Use 200 when you have data to return. According to RFC 9110, if you send 200 with zero-length payload, you should use 204 instead, as it more accurately represents the semantic intent.
Are 200 responses cacheable?
Yes, 200 responses are cacheable by default for GET, HEAD, and POST methods. However, you should control caching behavior with Cache-Control headers. Use 'Cache-Control: no-store' to prevent caching, 'max-age=3600' to cache for 1 hour, or 'private' to allow only browser caching (not shared caches like CDNs).
Is it okay to return 200 with an error object in the body?
No, this is an anti-pattern that violates HTTP semantics. If a request fails, use appropriate 4xx (client error) or 5xx (server error) status codes. Returning 200 with {\"status\": \"error\"} breaks caching, proxies, monitoring tools, and client error handling that rely on HTTP status codes. Always use proper status codes to represent the actual outcome.
How do ETags work with 200 responses?
ETags enable conditional requests to avoid transferring unchanged data. The server sends an ETag header with the 200 response (e.g., ETag: \"abc123\"). On subsequent requests, clients send If-None-Match: \"abc123\". If the resource hasn't changed, the server returns 304 Not Modified instead of 200, saving bandwidth by not re-sending the body.
Common Causes
- Successful GET request retrieving a resource (webpage, API data, image)
- Successful PUT/PATCH request updating an existing resource
- Successful DELETE request removing a resource (though 204 is often preferred)
- POST request that performs an action without creating a resource (search, login, calculation)
- OPTIONS request listing allowed methods for a resource
- Any successfully processed request with response data to return
Implementation Guidance
- For resource creation, use 201 Created instead of 200
- For successful operations with no response body, use 204 No Content instead
- For async operations, use 202 Accepted to indicate processing has started
- Always include appropriate Cache-Control headers to control caching behavior
- Use ETags (ETag header) to enable conditional requests and 304 Not Modified responses
- Never return 200 with error information in the body - use proper 4xx/5xx codes
- Include meaningful response payloads - if there’s nothing to return, use 204
- Set Vary header when response varies by request headers (e.g., Vary: Accept-Encoding)
- Enable compression for text responses to reduce bandwidth
- For paginated responses, include pagination metadata and next/prev links