0
# Request & Response Handling
1
2
Starlette provides comprehensive request parsing and response generation capabilities for HTTP operations, including JSON, forms, files, streaming, and more.
3
4
## HTTPConnection Base Class
5
6
```python { .api }
7
from starlette.requests import HTTPConnection
8
from starlette.types import Scope, Receive
9
from typing import Mapping, Any, Iterator
10
11
class HTTPConnection(Mapping[str, Any]):
12
"""
13
Base class for incoming HTTP connections, providing functionality
14
common to both Request and WebSocket.
15
16
Provides access to ASGI scope data and connection information.
17
"""
18
19
def __init__(self, scope: Scope, receive: Receive | None = None) -> None:
20
"""
21
Initialize HTTP connection from ASGI scope.
22
23
Args:
24
scope: ASGI connection scope
25
receive: Optional ASGI receive callable
26
"""
27
28
# Mapping interface for scope access
29
def __getitem__(self, key: str) -> Any:
30
"""Access scope values by key."""
31
32
def __iter__(self) -> Iterator[str]:
33
"""Iterate over scope keys."""
34
35
def __len__(self) -> int:
36
"""Get number of scope items."""
37
38
# Connection properties
39
@property
40
def url(self) -> URL:
41
"""Connection URL."""
42
43
@property
44
def base_url(self) -> URL:
45
"""Base URL without query parameters."""
46
47
@property
48
def headers(self) -> Headers:
49
"""Connection headers."""
50
51
@property
52
def query_params(self) -> QueryParams:
53
"""URL query parameters."""
54
55
@property
56
def path_params(self) -> dict[str, Any]:
57
"""Path parameters from URL routing."""
58
59
@property
60
def cookies(self) -> dict[str, str]:
61
"""Request cookies."""
62
63
@property
64
def client(self) -> Address | None:
65
"""Client address (host, port)."""
66
67
@property
68
def session(self) -> dict[str, Any]:
69
"""Session data (requires SessionMiddleware)."""
70
71
@property
72
def auth(self) -> Any:
73
"""Authentication data (requires AuthenticationMiddleware)."""
74
75
@property
76
def user(self) -> Any:
77
"""Authenticated user (requires AuthenticationMiddleware)."""
78
79
@property
80
def state(self) -> State:
81
"""Connection-scoped state storage."""
82
83
def url_for(self, name: str, /, **path_params: Any) -> URL:
84
"""Generate URL for named route."""
85
```
86
87
## Request Class
88
89
```python { .api }
90
from starlette.requests import Request, HTTPConnection, ClientDisconnect
91
from starlette.datastructures import URL, Headers, QueryParams, FormData, State, Address, MutableHeaders
92
from starlette.types import Scope, Receive, Send
93
from starlette._utils import AwaitableOrContextManager
94
from starlette.background import BackgroundTask
95
from datetime import datetime
96
from typing import Any, AsyncGenerator, Mapping, Literal
97
98
class Request(HTTPConnection):
99
"""
100
HTTP request object providing access to request data.
101
102
The Request class provides methods and properties for:
103
- HTTP method and URL information
104
- Headers and query parameters
105
- Request body parsing (JSON, forms, raw bytes)
106
- File uploads
107
- Cookies and sessions
108
- Client connection information
109
"""
110
111
def __init__(
112
self,
113
scope: Scope,
114
receive: Receive = empty_receive,
115
send: Send = empty_send
116
) -> None:
117
"""
118
Initialize request from ASGI scope.
119
120
Args:
121
scope: ASGI request scope
122
receive: ASGI receive callable
123
send: ASGI send callable (for push promises)
124
"""
125
126
# HTTP information
127
@property
128
def method(self) -> str:
129
"""HTTP method (GET, POST, etc.)."""
130
131
@property
132
def url(self) -> URL:
133
"""Full request URL including scheme, host, path, and query."""
134
135
@property
136
def base_url(self) -> URL:
137
"""Base URL (scheme + netloc) for generating absolute URLs."""
138
139
@property
140
def headers(self) -> Headers:
141
"""Request headers (case-insensitive)."""
142
143
@property
144
def query_params(self) -> QueryParams:
145
"""URL query parameters."""
146
147
@property
148
def path_params(self) -> dict[str, Any]:
149
"""Path parameters extracted from URL pattern."""
150
151
# Client information
152
@property
153
def cookies(self) -> dict[str, str]:
154
"""Request cookies."""
155
156
@property
157
def client(self) -> Address | None:
158
"""Client address (host, port)."""
159
160
# Extensions (require middleware)
161
@property
162
def session(self) -> dict[str, Any]:
163
"""Session data (requires SessionMiddleware)."""
164
165
@property
166
def auth(self) -> Any:
167
"""Authentication credentials (requires AuthenticationMiddleware)."""
168
169
@property
170
def user(self) -> Any:
171
"""Authenticated user object (requires AuthenticationMiddleware)."""
172
173
@property
174
def state(self) -> State:
175
"""Request-scoped state storage."""
176
177
@property
178
def receive(self) -> Receive:
179
"""ASGI receive callable."""
180
181
# Body parsing methods
182
def stream(self) -> AsyncGenerator[bytes, None]:
183
"""
184
Async generator yielding request body chunks.
185
186
Yields:
187
bytes: Raw body chunks as they arrive
188
"""
189
190
async def body(self) -> bytes:
191
"""
192
Get complete request body as bytes.
193
194
Returns:
195
bytes: Complete request body
196
"""
197
198
async def json(self) -> Any:
199
"""
200
Parse request body as JSON.
201
202
Returns:
203
Any: Parsed JSON data
204
205
Raises:
206
ValueError: If body is not valid JSON
207
"""
208
209
def form(
210
self,
211
*,
212
max_files: int | float = 1000,
213
max_fields: int | float = 1000,
214
max_part_size: int = 1024 * 1024,
215
) -> AwaitableOrContextManager[FormData]:
216
"""
217
Parse request body as form data (multipart or urlencoded).
218
219
Args:
220
max_files: Maximum number of file uploads
221
max_fields: Maximum number of form fields
222
max_part_size: Maximum size per multipart part
223
224
Returns:
225
FormData: Parsed form data with files and fields
226
"""
227
228
async def close(self) -> None:
229
"""Close any open file uploads in form data."""
230
231
async def is_disconnected(self) -> bool:
232
"""
233
Check if client has disconnected.
234
235
Returns:
236
bool: True if client disconnected
237
"""
238
239
async def send_push_promise(self, path: str) -> None:
240
"""
241
Send HTTP/2 server push promise.
242
243
Args:
244
path: Path to push to client
245
"""
246
247
def url_for(self, name: str, /, **path_params: Any) -> URL:
248
"""
249
Generate absolute URL for named route.
250
251
Args:
252
name: Route name
253
**path_params: Path parameter values
254
255
Returns:
256
URL: Absolute URL for the route
257
"""
258
```
259
260
## Response Classes
261
262
### Base Response
263
264
```python { .api }
265
from starlette.responses import Response
266
from starlette.background import BackgroundTask
267
from starlette.datastructures import MutableHeaders
268
from typing import Any, Iterable, Mapping
269
270
class Response:
271
"""
272
Base HTTP response class.
273
274
Provides the foundation for all HTTP responses with:
275
- Status code and headers management
276
- Content rendering
277
- Cookie handling
278
- Background task execution
279
"""
280
281
def __init__(
282
self,
283
content: Any = None,
284
status_code: int = 200,
285
headers: Mapping[str, str] | None = None,
286
media_type: str | None = None,
287
background: BackgroundTask | None = None,
288
) -> None:
289
"""
290
Initialize HTTP response.
291
292
Args:
293
content: Response content (rendered by subclass)
294
status_code: HTTP status code
295
headers: Response headers
296
media_type: Content-Type header value
297
background: Background task to execute after response
298
"""
299
300
@property
301
def status_code(self) -> int:
302
"""HTTP status code."""
303
304
@property
305
def headers(self) -> MutableHeaders:
306
"""Response headers (mutable)."""
307
308
@property
309
def media_type(self) -> str | None:
310
"""Content-Type media type."""
311
312
@property
313
def charset(self) -> str:
314
"""Character encoding (default: utf-8)."""
315
316
@property
317
def background(self) -> BackgroundTask | None:
318
"""Background task to execute."""
319
320
def render(self, content: Any) -> bytes | memoryview:
321
"""
322
Render content to bytes.
323
324
Override in subclasses for custom content rendering.
325
326
Args:
327
content: Content to render
328
329
Returns:
330
bytes | memoryview: Rendered content
331
"""
332
333
def init_headers(self, headers: Mapping[str, str] | None = None) -> None:
334
"""Initialize response headers."""
335
336
def set_cookie(
337
self,
338
key: str,
339
value: str = "",
340
max_age: int | None = None,
341
expires: datetime | str | int | None = None,
342
path: str | None = "/",
343
domain: str | None = None,
344
secure: bool = False,
345
httponly: bool = False,
346
samesite: Literal["lax", "strict", "none"] | None = "lax",
347
partitioned: bool = False,
348
) -> None:
349
"""
350
Set HTTP cookie.
351
352
Args:
353
key: Cookie name
354
value: Cookie value
355
max_age: Cookie lifetime in seconds
356
expires: Cookie expiration timestamp
357
path: Cookie path
358
domain: Cookie domain
359
secure: Only send over HTTPS
360
httponly: Prevent JavaScript access
361
samesite: SameSite policy ("strict", "lax", or "none")
362
partitioned: Partitioned cookie (Chrome extension)
363
"""
364
365
def delete_cookie(
366
self,
367
key: str,
368
path: str = "/",
369
domain: str | None = None,
370
secure: bool = False,
371
httponly: bool = False,
372
samesite: Literal["lax", "strict", "none"] | None = "lax",
373
) -> None:
374
"""
375
Delete HTTP cookie by setting it to expire.
376
377
Args:
378
key: Cookie name to delete
379
path: Cookie path (must match original)
380
domain: Cookie domain (must match original)
381
secure: Secure flag (must match original)
382
httponly: HttpOnly flag (must match original)
383
samesite: SameSite policy (must match original)
384
"""
385
```
386
387
### JSON Response
388
389
```python { .api }
390
from starlette.responses import JSONResponse
391
import json
392
393
class JSONResponse(Response):
394
"""
395
JSON response with application/json media type.
396
397
Automatically serializes content to JSON and sets appropriate headers.
398
"""
399
400
media_type = "application/json"
401
402
def render(self, content: Any) -> bytes:
403
"""
404
Serialize content to JSON bytes.
405
406
Args:
407
content: JSON-serializable data
408
409
Returns:
410
bytes: JSON-encoded content
411
"""
412
return json.dumps(
413
content,
414
ensure_ascii=False,
415
allow_nan=False,
416
indent=None,
417
separators=(",", ":"),
418
).encode("utf-8")
419
```
420
421
### Text Responses
422
423
```python { .api }
424
from starlette.responses import PlainTextResponse, HTMLResponse
425
426
class PlainTextResponse(Response):
427
"""Plain text response with text/plain media type."""
428
429
media_type = "text/plain"
430
431
class HTMLResponse(Response):
432
"""HTML response with text/html media type."""
433
434
media_type = "text/html"
435
```
436
437
### Redirect Response
438
439
```python { .api }
440
from starlette.responses import RedirectResponse
441
442
class RedirectResponse(Response):
443
"""
444
HTTP redirect response.
445
446
Returns a redirect to another URL with appropriate status code.
447
"""
448
449
def __init__(
450
self,
451
url: str,
452
status_code: int = 307,
453
headers: Mapping[str, str] | None = None,
454
background: BackgroundTask | None = None,
455
) -> None:
456
"""
457
Initialize redirect response.
458
459
Args:
460
url: Target URL for redirect
461
status_code: HTTP redirect status (301, 302, 307, 308)
462
headers: Additional response headers
463
background: Background task to execute
464
"""
465
```
466
467
### Streaming Response
468
469
```python { .api }
470
from starlette.responses import StreamingResponse
471
from typing import AsyncIterable, Iterable, Union
472
473
# Content stream type aliases
474
SyncContentStream = Iterable[str | bytes]
475
AsyncContentStream = AsyncIterable[str | bytes]
476
ContentStream = Union[AsyncContentStream, SyncContentStream]
477
478
class StreamingResponse(Response):
479
"""
480
Streaming HTTP response for large or generated content.
481
482
Streams content to client as it becomes available, useful for:
483
- Large files
484
- Real-time generated data
485
- Server-sent events
486
- Reducing memory usage
487
"""
488
489
def __init__(
490
self,
491
content: ContentStream,
492
status_code: int = 200,
493
headers: Mapping[str, str] | None = None,
494
media_type: str | None = None,
495
background: BackgroundTask | None = None,
496
) -> None:
497
"""
498
Initialize streaming response.
499
500
Args:
501
content: Iterable or async iterable yielding chunks
502
status_code: HTTP status code
503
headers: Response headers
504
media_type: Content-Type header
505
background: Background task to execute
506
"""
507
508
async def listen_for_disconnect(self, receive: Receive) -> None:
509
"""Listen for client disconnect during streaming."""
510
511
async def stream_response(self, send: Send) -> None:
512
"""Stream response content to client."""
513
```
514
515
### File Response
516
517
```python { .api }
518
from starlette.responses import FileResponse
519
from os import PathLike
520
from typing import BinaryIO
521
522
class FileResponse(Response):
523
"""
524
File download response with automatic headers.
525
526
Efficiently serves files with proper Content-Type, Content-Length,
527
and caching headers.
528
"""
529
530
chunk_size = 64 * 1024 # 64KB chunks
531
532
def __init__(
533
self,
534
path: str | PathLike[str],
535
status_code: int = 200,
536
headers: Mapping[str, str] | None = None,
537
media_type: str | None = None,
538
background: BackgroundTask | None = None,
539
filename: str | None = None,
540
stat_result: os.stat_result | None = None,
541
method: str | None = None,
542
content_disposition_type: str = "attachment",
543
) -> None:
544
"""
545
Initialize file response.
546
547
Args:
548
path: File system path to serve
549
status_code: HTTP status code
550
headers: Additional headers
551
media_type: Override Content-Type detection
552
background: Background task to execute
553
filename: Override filename for Content-Disposition
554
stat_result: Pre-computed file stats for performance
555
method: HTTP method (affects HEAD handling)
556
content_disposition_type: "attachment" or "inline"
557
"""
558
559
def set_stat_headers(self, stat_result: os.stat_result) -> None:
560
"""Set Content-Length and Last-Modified headers from file stats."""
561
562
@staticmethod
563
def is_not_modified(
564
response_headers: Headers,
565
request_headers: Headers,
566
) -> bool:
567
"""Check if file has been modified based on headers."""
568
```
569
570
## Request Data Access
571
572
### Basic Request Information
573
574
```python { .api }
575
async def request_info(request: Request):
576
return JSONResponse({
577
"method": request.method,
578
"url": str(request.url),
579
"path": request.url.path,
580
"query": str(request.query_params),
581
"headers": dict(request.headers),
582
"client": request.client and request.client.host,
583
})
584
```
585
586
### Query Parameters
587
588
```python { .api }
589
from starlette.datastructures import QueryParams
590
591
async def search_endpoint(request: Request):
592
# Access query parameters
593
query = request.query_params.get("q", "")
594
page = request.query_params.get("page", "1")
595
per_page = request.query_params.get("per_page", "10")
596
597
# Convert to appropriate types
598
try:
599
page = int(page)
600
per_page = int(per_page)
601
except ValueError:
602
return JSONResponse({"error": "Invalid parameters"}, status_code=400)
603
604
# Multiple values for same parameter
605
tags = request.query_params.getlist("tag") # ?tag=python&tag=web
606
607
return JSONResponse({
608
"query": query,
609
"page": page,
610
"per_page": per_page,
611
"tags": tags,
612
})
613
```
614
615
### Path Parameters
616
617
```python { .api }
618
from starlette.routing import Route
619
620
async def user_posts(request: Request):
621
# Extract path parameters
622
user_id = int(request.path_params["user_id"])
623
624
# Optional parameters
625
post_id = request.path_params.get("post_id")
626
if post_id:
627
post_id = int(post_id)
628
629
return JSONResponse({
630
"user_id": user_id,
631
"post_id": post_id,
632
})
633
634
routes = [
635
Route("/users/{user_id:int}", user_posts),
636
Route("/users/{user_id:int}/posts/{post_id:int}", user_posts),
637
]
638
```
639
640
### Request Headers
641
642
```python { .api }
643
async def header_info(request: Request):
644
# Case-insensitive header access
645
content_type = request.headers.get("content-type")
646
user_agent = request.headers.get("user-agent")
647
authorization = request.headers.get("authorization")
648
649
# Custom headers
650
api_key = request.headers.get("x-api-key")
651
652
# Get all values for header (rare)
653
accept_values = request.headers.getlist("accept")
654
655
return JSONResponse({
656
"content_type": content_type,
657
"user_agent": user_agent,
658
"has_auth": bool(authorization),
659
"api_key": bool(api_key),
660
"accept": accept_values,
661
})
662
```
663
664
### Cookies
665
666
```python { .api }
667
async def cookie_info(request: Request):
668
# Access cookies
669
session_id = request.cookies.get("session_id")
670
preferences = request.cookies.get("preferences", "{}")
671
672
return JSONResponse({
673
"session_id": bool(session_id),
674
"preferences": preferences,
675
"all_cookies": list(request.cookies.keys()),
676
})
677
```
678
679
## Request Body Parsing
680
681
### JSON Data
682
683
```python { .api }
684
async def json_endpoint(request: Request):
685
try:
686
# Parse JSON body
687
data = await request.json()
688
689
# Process data
690
result = {
691
"received": data,
692
"type": type(data).__name__,
693
}
694
695
return JSONResponse(result)
696
697
except ValueError as e:
698
return JSONResponse(
699
{"error": "Invalid JSON", "details": str(e)},
700
status_code=400
701
)
702
```
703
704
### Form Data
705
706
```python { .api }
707
async def form_endpoint(request: Request):
708
# Parse form data (application/x-www-form-urlencoded or multipart/form-data)
709
form = await request.form()
710
711
# Access form fields
712
name = form.get("name", "")
713
email = form.get("email", "")
714
715
# Multiple values
716
interests = form.getlist("interest") # Multiple checkboxes/select
717
718
# File uploads
719
avatar = form.get("avatar") # UploadFile object
720
721
response_data = {
722
"name": name,
723
"email": email,
724
"interests": interests,
725
}
726
727
# Handle file upload
728
if avatar and avatar.filename:
729
# Read file content
730
content = await avatar.read()
731
response_data["avatar"] = {
732
"filename": avatar.filename,
733
"content_type": avatar.content_type,
734
"size": len(content),
735
}
736
737
# Close file to free memory
738
await avatar.close()
739
740
# Close form to clean up any remaining files
741
await form.close()
742
743
return JSONResponse(response_data)
744
```
745
746
### File Uploads
747
748
```python { .api }
749
from starlette.datastructures import UploadFile
750
import os
751
752
async def upload_endpoint(request: Request):
753
form = await request.form()
754
755
uploaded_files = []
756
757
# Process multiple file uploads
758
for field_name, file in form.items():
759
if isinstance(file, UploadFile) and file.filename:
760
# Save file to disk
761
file_path = f"uploads/{file.filename}"
762
os.makedirs("uploads", exist_ok=True)
763
764
with open(file_path, "wb") as f:
765
# Read in chunks to handle large files
766
while chunk := await file.read(1024):
767
f.write(chunk)
768
769
uploaded_files.append({
770
"field": field_name,
771
"filename": file.filename,
772
"content_type": file.content_type,
773
"size": file.size,
774
"path": file_path,
775
})
776
777
await file.close()
778
779
await form.close()
780
781
return JSONResponse({"uploaded_files": uploaded_files})
782
```
783
784
### Raw Body Data
785
786
```python { .api }
787
async def raw_body_endpoint(request: Request):
788
# Get complete body as bytes
789
body = await request.body()
790
791
return JSONResponse({
792
"body_size": len(body),
793
"content_type": request.headers.get("content-type"),
794
})
795
796
async def streaming_body_endpoint(request: Request):
797
# Process body in chunks (memory efficient for large uploads)
798
chunks = []
799
total_size = 0
800
801
async for chunk in request.stream():
802
chunks.append(len(chunk))
803
total_size += len(chunk)
804
805
# Process chunk...
806
807
# Early termination if too large
808
if total_size > 10 * 1024 * 1024: # 10MB limit
809
return JSONResponse(
810
{"error": "Request too large"},
811
status_code=413
812
)
813
814
return JSONResponse({
815
"chunks": len(chunks),
816
"total_size": total_size,
817
})
818
```
819
820
## Response Generation
821
822
### JSON Responses
823
824
```python { .api }
825
async def api_endpoint(request: Request):
826
# Simple JSON response
827
return JSONResponse({"message": "success"})
828
829
async def api_with_status(request: Request):
830
# JSON with custom status code
831
return JSONResponse(
832
{"error": "Resource not found"},
833
status_code=404
834
)
835
836
async def api_with_headers(request: Request):
837
# JSON with custom headers
838
return JSONResponse(
839
{"data": "value"},
840
headers={"X-Custom-Header": "value"}
841
)
842
```
843
844
### Text and HTML Responses
845
846
```python { .api }
847
async def text_endpoint(request: Request):
848
return PlainTextResponse("Hello, plain text!")
849
850
async def html_endpoint(request: Request):
851
html_content = """
852
<!DOCTYPE html>
853
<html>
854
<head><title>Hello</title></head>
855
<body><h1>Hello, HTML!</h1></body>
856
</html>
857
"""
858
return HTMLResponse(html_content)
859
```
860
861
### Redirects
862
863
```python { .api }
864
async def redirect_endpoint(request: Request):
865
# Temporary redirect (307)
866
return RedirectResponse("/new-location")
867
868
async def permanent_redirect(request: Request):
869
# Permanent redirect (301)
870
return RedirectResponse("/new-location", status_code=301)
871
872
async def redirect_with_params(request: Request):
873
# Redirect to named route
874
user_id = request.path_params["user_id"]
875
redirect_url = request.url_for("user_profile", user_id=user_id)
876
return RedirectResponse(redirect_url)
877
```
878
879
### File Downloads
880
881
```python { .api }
882
async def download_file(request: Request):
883
file_path = "documents/report.pdf"
884
885
# Simple file download
886
return FileResponse(file_path, filename="monthly_report.pdf")
887
888
async def download_with_headers(request: Request):
889
# File download with custom headers
890
return FileResponse(
891
"data/export.csv",
892
media_type="text/csv",
893
headers={"X-Custom-Header": "value"},
894
filename="data_export.csv"
895
)
896
897
async def inline_file(request: Request):
898
# Display file inline (not download)
899
return FileResponse(
900
"images/chart.png",
901
content_disposition_type="inline"
902
)
903
```
904
905
### Streaming Responses
906
907
```python { .api }
908
import asyncio
909
910
async def stream_endpoint(request: Request):
911
# Stream generated data
912
async def generate_data():
913
for i in range(100):
914
yield f"data chunk {i}\n"
915
await asyncio.sleep(0.1) # Simulate processing
916
917
return StreamingResponse(
918
generate_data(),
919
media_type="text/plain"
920
)
921
922
async def csv_stream(request: Request):
923
# Stream CSV data
924
async def generate_csv():
925
yield "id,name,email\n"
926
927
# Simulate database streaming
928
for i in range(1000):
929
yield f"{i},User {i},user{i}@example.com\n"
930
if i % 100 == 0:
931
await asyncio.sleep(0.01) # Yield control
932
933
return StreamingResponse(
934
generate_csv(),
935
media_type="text/csv",
936
headers={"Content-Disposition": "attachment; filename=users.csv"}
937
)
938
939
async def server_sent_events(request: Request):
940
# Server-Sent Events stream
941
async def event_stream():
942
counter = 0
943
while True:
944
# Check if client disconnected
945
if await request.is_disconnected():
946
break
947
948
# Send event
949
yield f"data: Event {counter}\n\n"
950
counter += 1
951
await asyncio.sleep(1)
952
953
return StreamingResponse(
954
event_stream(),
955
media_type="text/event-stream",
956
headers={
957
"Cache-Control": "no-cache",
958
"Connection": "keep-alive",
959
}
960
)
961
```
962
963
## Cookies and Sessions
964
965
### Setting Cookies
966
967
```python { .api }
968
async def set_cookie_endpoint(request: Request):
969
response = JSONResponse({"message": "Cookie set"})
970
971
# Simple cookie
972
response.set_cookie("simple", "value")
973
974
# Cookie with options
975
response.set_cookie(
976
key="preferences",
977
value="dark_theme",
978
max_age=30 * 24 * 60 * 60, # 30 days
979
secure=True,
980
httponly=True,
981
samesite="strict"
982
)
983
984
return response
985
986
async def delete_cookie_endpoint(request: Request):
987
response = JSONResponse({"message": "Cookie deleted"})
988
response.delete_cookie("preferences")
989
return response
990
```
991
992
### Background Tasks
993
994
```python { .api }
995
from starlette.background import BackgroundTask
996
997
def send_email(to: str, subject: str, body: str):
998
# Simulate sending email
999
print(f"Sending email to {to}: {subject}")
1000
1001
async def user_signup(request: Request):
1002
data = await request.json()
1003
1004
# Create user immediately
1005
user_id = create_user(data)
1006
1007
# Send welcome email in background
1008
task = BackgroundTask(
1009
send_email,
1010
to=data["email"],
1011
subject="Welcome!",
1012
body="Thanks for signing up!"
1013
)
1014
1015
return JSONResponse(
1016
{"user_id": user_id, "message": "User created"},
1017
background=task
1018
)
1019
```
1020
1021
Starlette's request and response system provides comprehensive tools for handling HTTP communication with proper parsing, validation, and response generation capabilities.