or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mdcore-application.mddata-structures.mdexceptions-status.mdindex.mdmiddleware.mdrequests-responses.mdrouting.mdstatic-files.mdtesting.mdwebsockets.md

requests-responses.mddocs/

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.