Repository: sindresorhus/got (ts)

Issue:
Basic documentation for 'got' object
I don't know if I'm looking in the wrong place, but although this seems to be a pretty well-documented project, I am completely unable to find the basic documentation on the got object itself, such as the parameters for `got.post()` and other similar methods (presumably `get`, `put`, `patch`, etc.).

I've read through everything in the documentation folder and while its very useful to know how to manage streams, I'm struggling to find the basics, like whether I need to set a header when posting json or if that's automatic. 

I'm sure it's here somewhere, I just can't find it.

Pull request:
Quick start
#### Checklist

- [X] I have read the documentation.
- [X] I have included a pull request description of my changes.
- [ ] I have included some tests.
- [ ] If it's a new feature, I have included documentation updates in both the README and the types.

Quick start guide. Fix #2016.

Patch:
diff --git a/documentation/3-streams.md b/documentation/3-streams.md
index 9070e43..5fe237c 100644
--- a/documentation/3-streams.md
+++ b/documentation/3-streams.md
@@ -239,6 +239,11 @@ To enable retrying when using streams, a retry handler must be attached.
 
 When this event is emitted, you should reset the stream you were writing to and prepare the body again.
 
+**Note:**
+> - [`HTTPError`s](./8-errors.md#httperror) cannot be retried if [`options.throwHttpErrors`](./2-options.md#throwhttperrors) is `false`.
+>   This is because stream data is saved to `error.response.body` and streams can be read only once.
+> - For the Promise API, there is no such limitation.
+
 #### `retryCount`
 
 **Type: `number`**
diff --git a/documentation/quick-start.md b/documentation/quick-start.md
new file mode 100644
index 0000000..4785a9b
--- /dev/null
+++ b/documentation/quick-start.md
@@ -0,0 +1,234 @@
+# Quick Start Guide
+
+## Getting and posting data with promises
+
+The simplest `GET` request:
+
+```js
+import got from 'got';
+
+const url = 'https://httpbin.org/anything';
+const response = await got(url);
+```
+
+The call returns a <code>Promise<[Response](3-streams.md#response-1)></code>. If the body contains JSON, it can be retrieved directly:
+
+```js
+import got from 'got';
+
+const url = 'https://httpbin.org/anything';
+const data = await got(url).json();
+```
+
+The similar <code>[got.text](1-promise.md#promisetext)</code> method returns plain text.
+
+All `got` methods accept an options object for passing extra configuration, such as headers:
+
+```js
+import got from 'got';
+
+const url = 'https://httpbin.org/anything';
+
+const options = {
+	headers: {
+		'Custom-Header': 'Quick start',
+	},
+	timeout: {
+		send: 3500
+	},
+};
+
+const data = await got(url, options).json();
+```
+
+A `POST` request is very similar:
+
+```js
+import got from 'got';
+
+const url = 'https://httpbin.org/anything';
+
+const options = {
+	json: {
+		documentName: 'Quick Start',
+	},
+};
+
+const data = await got.post(url, options);
+```
+
+The request body is passed in the options object. The `json` property will automatically set headers accordingly. Custom headers can be added exactly as above.
+
+## Using streams
+
+The [Stream API](3-streams.md) allows to leverage [Node.js Streams](https://nodejs.dev/learn/nodejs-streams) capabilities:
+
+```js
+import fs from 'node:fs';
+import {pipeline} from 'node:stream/promises';
+import got from 'got';
+
+const url = 'https://httpbin.org/anything';
+
+const options = {
+	json: {
+		documentName: 'Quick Start',
+	},
+};
+
+const gotStream = got.stream.post(url, options);
+
+const outStream = fs.createWriteStream('anything.json');
+
+try {
+	await pipeline(gotStream, outStream);
+} catch (error) {
+	console.error(error);
+}
+```
+
+## Options
+
+Options can be set at the client level and reused in subsequent queries:
+
+```js
+import got from 'got';
+
+const options = {
+	prefixUrl: 'https://httpbin.org',
+	headers: {
+		Authorization: getTokenFromVault(),
+	},
+};
+
+const client = got.extend(options);
+
+export default client;
+```
+
+Some noticeable common options are:
+- [`searchParams`](2-options.md#searchparams): A query string object.
+- [`prefixUrl`](2-options.md#prefixurl): Prepended to query paths. Paths must be relative to prefix, i.e. not begin with a `/`.
+- [`method`](2-options.md#method): The HTTP method name.
+- [`headers`](2-options.md#headers): Query headers.
+- [`json`](2-options.md#json): JSON body.
+- [`form`](2-options.md#form): A form query string object.
+
+See the documentation for other [options](2-options.md#options).
+
+## Errors
+
+Both Promise and Stream APIs throw errors with metadata.
+
+```js
+import got from 'got';
+
+try {
+	const data = await got.get('https://httpbin.org/status/404');
+} catch (error) {
+	console.error(error.response.statusCode);
+}
+```
+
+```js
+import got from 'got';
+
+const stream = got.stream
+	.get('https://httpbin.org/status/404')
+	.once('error', error => {
+		console.error(error.response.statusCode);
+	});
+```
+
+## Miscellaneous
+
+The HTTP method name can also be given as an option, this may be convenient when it is known only at runtime:
+
+```js
+import got from 'got';
+
+const url = 'https://httpbin.org/anything';
+
+const method = 'POST';
+
+const options = {
+	method,
+	json: {
+		documentName: 'Quick Start',
+	},
+};
+
+const data = await got(url, options);
+```
+
+For most apps, HTTP clients just do `GET` and `POST` queries (`PUT`, `PATCH` or `DELETE` methods work similarly).
+The following sections will give some pointers to more advanced usage.
+
+### Timeouts
+
+By default, requests have no timeout. It is a good practice to set one:
+
+```js
+import got from 'got';
+
+const options = {
+	timeout: {
+		request: 10000,
+	},
+};
+
+const client = got.extend(options);
+
+export default client;
+```
+
+The above sets a global timeout of 10000 milliseconds for all requests issued by the exported `client`. Like all options, timeouts can also be set at the request level. See the [`timeout` option](6-timeout.md#timeout-options).
+
+### Retries
+
+A failed request is retried twice. The retry policy may be tuned with a [`retry`](7-retry.md#retry) options object.
+
+```js
+import got from 'got';
+
+const options = {
+	retry: {
+		limit: 5,
+		errorCodes: [
+			'ETIMEDOUT'
+		],
+	},
+};
+```
+
+Retries with stream are a little trickier, see [`stream.on('retry', …)`](3-streams.md#streamonretry-).
+
+### Hooks
+
+Hooks are custom functions called on some request events:
+
+```js
+import got from 'got';
+
+const logRetry = (error, retryCount) => {
+	console.error(`Retrying after error ${error.code}, retry #: ${retryCount}`);
+};
+
+const options = {
+	hooks: {
+		beforeRetry: [
+			logRetry,
+		],
+	},
+};
+
+const client = got.extend(options);
+
+export default client;
+```
+
+*Note that hooks are given as arrays*, thus multiple hooks can be given. See documentation for other possible [hooks](9-hooks.md#hooks-api).
+
+### Going further
+
+There is a lot more to discover in the [documentation](../readme.md#documentation) and [tips](tips.md#tips). Among others, `Got` can handle [cookies](tips.md#cookies), [pagination](4-pagination.md#pagination-api), [cache](cache.md#cache). Please read the documentation before implementing something that is already done by `Got` :innocent:.
diff --git a/readme.md b/readme.md
index 23215ee..3cf10a8 100644
--- a/readme.md
+++ b/readme.md
@@ -109,6 +109,8 @@ npm install got
 
 ## Take a peek
 
+**A [quick start](documentation/quick-start.md) guide is available.**
+
 ### JSON mode
 
 Got has a dedicated option for handling JSON payload.\
diff --git a/source/core/index.ts b/source/core/index.ts
index edb1246..4ead97a 100644
--- a/source/core/index.ts
+++ b/source/core/index.ts
@@ -797,6 +797,10 @@ export default class Request extends Duplex implements RequestEvents<Request> {
 			return;
 		}
 
+		// `HTTPError`s always have `error.response.body` defined.
+		// Therefore we cannot retry if `options.throwHttpErrors` is false.
+		// On the last retry, if `options.throwHttpErrors` is false, we would need to return the body,
+		// but that wouldn't be possible since the body would be already read in `error.response.body`.
 		if (options.isStream && options.throwHttpErrors && !isResponseOk(typedResponse)) {
 			this._beforeError(new HTTPError(typedResponse));
 			return;
@@ -1148,9 +1152,15 @@ export default class Request extends Duplex implements RequestEvents<Request> {
 
 	private async _error(error: RequestError): Promise<void> {
 		try {
-			for (const hook of this.options.hooks.beforeError) {
-				// eslint-disable-next-line no-await-in-loop
-				error = await hook(error);
+			if (error instanceof HTTPError && !this.options.throwHttpErrors) {
+				// This branch can be reached only when using the Promise API
+				// Skip calling the hooks on purpose.
+				// See https://github.com/sindresorhus/got/issues/2103
+			} else {
+				for (const hook of this.options.hooks.beforeError) {
+					// eslint-disable-next-line no-await-in-loop
+					error = await hook(error);
+				}
 			}
 		} catch (error_: any) {
 			error = new RequestError(error_.message, error_, this);


Test patch:
diff --git a/test/hooks.ts b/test/hooks.ts
index fab442a..2241137 100644
--- a/test/hooks.ts
+++ b/test/hooks.ts
@@ -1368,3 +1368,26 @@ test('does not throw on empty body when running afterResponse hooks', withServer
 		},
 	}));
 });
+
+test('does not call beforeError hooks on falsy throwHttpErrors', withServer, async (t, server, got) => {
+	server.get('/', (_request, response) => {
+		response.statusCode = 404;
+		response.end();
+	});
+
+	let called = false;
+
+	await got('', {
+		throwHttpErrors: false,
+		hooks: {
+			beforeError: [
+				error => {
+					called = true;
+					return error;
+				},
+			],
+		},
+	});
+
+	t.false(called);
+});