Request & Response Objects
In Fastay, every route handler receives Request and Response objects that provide an intuitive interface for handling HTTP communication. These objects are enhanced versions of Express.js equivalents with additional features and better TypeScript support.
Overview
Fastay's Request and Response objects simplify HTTP handling while giving you complete control. Each route handler automatically receives these objects:
import { Request, Response } from "@syntay/fastay";
export async function GET(request: Request, response: Response) {
// Handle GET requests
}
export async function POST(request: Request, response: Response) {
// Handle POST requests
}
Key Features
- TypeScript First: Full TypeScript support with proper typing
- Enhanced Methods: Additional methods beyond standard Express
- Intuitive API: Simplified access to common operations
- Automatic Parsing: Request bodies are automatically parsed based on Content-Type
The Request Object
The Request object contains all information about the incoming HTTP request.
Accessing Request Data
export async function GET(request: Request) {
// HTTP Method
const method = request.method; // "GET", "POST", etc.
// URL Information
const url = request.url; // Full URL
const path = request.path; // Path without query string
// Headers
const authHeader = request.headers.authorization;
const contentType = request.get("content-type");
// Client Information
const clientIp = request.ip;
const hostname = request.hostname;
return {
requestInfo: {
method,
path,
clientIp,
timestamp: new Date().toISOString(),
},
};
}
Query Parameters
Fastay automatically parses query strings from URLs:
// Example: GET /api/users?role=admin&page=2&limit=20
export async function GET(request: Request) {
// Access all query parameters
const query = request.query;
// { role: "admin", page: "2", limit: "20" }
// Access specific parameters
const role = request.query.role;
const page = request.query.page || "1";
const limit = request.query.limit || "20";
// Parse numbers
const pageNum = parseInt(page);
const limitNum = parseInt(limit);
return {
filters: {
role,
page: pageNum,
limit: limitNum,
},
};
}
Route Parameters
For dynamic routes, parameters are automatically extracted:
// File: /api/users/[userId]/route.ts
// URL: /api/users/123
export async function GET(request: Request) {
// Access the dynamic parameter
const userId = request.params.userId;
// Validate and convert
if (!userId || isNaN(parseInt(userId))) {
return {
status: 400,
body: { error: "Invalid user ID" },
};
}
const id = parseInt(userId);
return {
userId: id,
message: `Fetching user with ID: ${id}`,
};
}
Request Body
Fastay automatically parses request bodies based on Content-Type:
// JSON Body (Content-Type: application/json)
export async function POST(request: Request) {
const body = await request.body;
// Already parsed as JavaScript object
// TypeScript with interface
interface CreateUserDto {
name: string;
email: string;
age?: number;
}
const userData = body as CreateUserDto;
return {
message: "User data received",
data: userData,
};
}
FormData (File Uploads)
For file uploads and form data:
// Content-Type: multipart/form-data
export async function POST(request: Request) {
const formData = await request.formData();
// Get form fields
const name = formData.get("name") as string;
const email = formData.get("email") as string;
// Get uploaded file
const file = formData.get("avatar") as File;
if (file) {
// File information
const fileInfo = {
name: file.name,
type: file.type,
size: file.size,
// Temporary path where file is stored
tempPath: file.path,
};
// Process the file...
}
return {
message: "Form submitted",
data: { name, email, hasAvatar: !!file },
};
}
Cookies
Fastay provides convenient cookie handling:
export async function GET(request: Request) {
// Check if a cookie exists
const hasSession = request.cookies.has("session_id");
// Get cookie value
const sessionId = request.cookies.get("session_id");
// Get all cookies
const allCookies = request.cookies.all();
// { session_id: "abc123", user_prefs: "dark_mode" }
return {
authenticated: hasSession,
sessionId,
cookies: allCookies,
};
}
The Response Object
The Response object is used to send HTTP responses back to the client.
Basic Response Methods
export async function GET(request: Request, response: Response) {
// 1. Send JSON response (most common)
return response.json({
success: true,
data: { id: 1, name: "Fastay" },
});
// 2. Send plain text
return response.send("Hello, World!");
// 3. Set status code before sending
response.status(201); // Created
return response.json({
message: "Resource created",
});
}
Setting Headers
export async function GET(request: Request, response: Response) {
// Set individual headers
response.setHeader("Content-Type", "application/json");
response.setHeader("X-API-Version", "1.0.0");
response.setHeader("Cache-Control", "public, max-age=3600");
// Set multiple headers at once
response.set({
"Content-Type": "application/json",
"X-Powered-By": "Fastay",
"X-Request-ID": generateRequestId(),
});
return response.json({ message: "Headers set" });
}
Setting Cookies
export async function POST(request: Request, response: Response) {
// Set a cookie
response.cookie("session_token", "encrypted_token_value", {
httpOnly: true, // Not accessible via JavaScript
secure: process.env.NODE_ENV === "production", // HTTPS only in production
sameSite: "strict", // CSRF protection
maxAge: 24 * 60 * 60 * 1000, // 24 hours in milliseconds
path: "/", // Available on all routes
});
// Set a cookie with domain (production only)
if (process.env.NODE_ENV === "production") {
response.cookie("user_prefs", "dark_mode", {
domain: ".example.com", // Available on all subdomains
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
});
}
// Clear a cookie
response.clearCookie("old_session");
return response.json({
message: "Authentication successful",
cookiesSet: true,
});
}
Advanced Response Patterns
Fastay supports advanced response patterns for different use cases:
export async function GET(request: Request, response: Response) {
const acceptHeader = request.get("accept");
// Content negotiation
if (acceptHeader?.includes("application/xml")) {
response.setHeader("Content-Type", "application/xml");
return response.send(`
<?xml version="1.0"?>
<response>
<status>success</status>
<message>XML response</message>
</response>
`);
}
if (acceptHeader?.includes("text/html")) {
response.setHeader("Content-Type", "text/html");
return response.send(`
<!DOCTYPE html>
<html>
<head><title>API Response</title></head>
<body>
<h1>HTML Response</h1>
<p>This is an HTML response from the API</p>
</body>
</html>
`);
}
// Default to JSON
return response.json({
status: "success",
message: "JSON response",
format: "json",
});
}
Response Format in Fastay
Fastay provides a flexible response system. You can return data in several ways:
Simple Return Values
// Return JSON object (automatically sets Content-Type: application/json)
export async function GET() {
return { message: "Hello" };
}
// Return string
export async function GET() {
return "Plain text response";
}
// Return number
export async function GET() {
return 42; // Becomes "42" with Content-Type: text/plain
}
// Return array
export async function GET() {
return [1, 2, 3, 4, 5];
}
// Return null or undefined (204 No Content)
export async function DELETE() {
// Perform delete operation...
return null; // Returns 204 No Content
}
Advanced Response Configuration
For more control, return a response configuration object:
export async function POST(request: Request) {
const data = await request.body;
return {
// HTTP status code
status: 201, // Created
// Response body
body: {
success: true,
data: data,
message: "Resource created successfully",
},
// Custom headers
headers: {
"Content-Type": "application/json",
"X-Custom-Header": "value",
Location: `/api/resources/${data.id}`,
},
// Set cookies
cookies: {
auth_token: {
value: "jwt_token_here",
options: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 24 * 60 * 60 * 1000,
sameSite: "strict",
},
},
},
};
}
Special Response Types
File Download
export async function GET() {
const filePath = "/path/to/document.pdf";
return {
file: {
path: filePath,
downloadName: "document.pdf",
contentType: "application/pdf",
},
};
}
Redirect
export async function GET() {
// Temporary redirect (302)
return {
redirect: "/new-location",
status: 302,
};
// Permanent redirect (301)
return {
redirect: "https://new-domain.com",
status: 301,
};
}
Stream Response
import fs from "fs";
export async function GET() {
const videoPath = "/path/to/video.mp4";
return {
stream: fs.createReadStream(videoPath),
headers: {
"Content-Type": "video/mp4",
"Content-Length": fs.statSync(videoPath).size,
},
};
}
Practical Examples
User Registration Endpoint
import { Request, Response } from "@syntay/fastay";
export async function POST(request: Request, response: Response) {
try {
const userData = await request.body;
// Validation
if (!userData.email || !userData.password) {
return response.status(400).json({
error: "Email and password are required",
});
}
// Create user in database
const user = await database.users.create({
data: {
email: userData.email,
passwordHash: await hashPassword(userData.password),
name: userData.name,
},
});
// Generate JWT token
const token = generateJWT({ userId: user.id });
// Set authentication cookie
response.cookie("auth_token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
sameSite: "strict",
});
// Return success response
return response.status(201).json({
message: "User registered successfully",
user: {
id: user.id,
email: user.email,
name: user.name,
},
});
} catch (error) {
console.error("Registration error:", error);
// Handle duplicate email
if (error.code === "P2002") {
// Prisma unique constraint
return response.status(409).json({
error: "Email already registered",
});
}
return response.status(500).json({
error: "Registration failed",
});
}
}
File Upload Handler
export async function POST(request: Request) {
const formData = await request.formData();
const title = formData.get("title") as string;
const description = formData.get("description") as string;
const file = formData.get("file") as File;
if (!title || !file) {
return {
status: 400,
body: { error: "Title and file are required" },
};
}
// Generate unique filename
const fileExt = file.name.split(".").pop();
const fileName = `${Date.now()}-${Math.random()
.toString(36)
.substr(2)}.${fileExt}`;
const filePath = `/uploads/${fileName}`;
// Move file to permanent storage
await saveFile(file, filePath);
return {
status: 200,
body: {
message: "File uploaded successfully",
file: {
originalName: file.name,
storedName: fileName,
size: file.size,
type: file.type,
url: `/files/${fileName}`,
},
},
};
}
Best Practices
1. Validate Input Early
export async function POST(request: Request) {
const data = await request.body;
// Validate required fields
const requiredFields = ["name", "email", "password"];
const missingFields = requiredFields.filter((field) => !data[field]);
if (missingFields.length > 0) {
return {
status: 400,
body: {
error: "Missing required fields",
missing: missingFields,
},
};
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(data.email)) {
return {
status: 400,
body: { error: "Invalid email format" },
};
}
// Process valid data...
}
2. Use Appropriate Status Codes
export async function handleRequest(request: Request, response: Response) {
// Success codes
response.status(200); // OK
response.status(201); // Created
response.status(204); // No Content
// Client error codes
response.status(400); // Bad Request
response.status(401); // Unauthorized
response.status(403); // Forbidden
response.status(404); // Not Found
response.status(409); // Conflict
// Server error codes
response.status(500); // Internal Server Error
response.status(503); // Service Unavailable
}
3. Set Security Headers
export async function GET(request: Request, response: Response) {
// Security headers
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-Frame-Options", "DENY");
response.setHeader("X-XSS-Protection", "1; mode=block");
// CORS headers if needed
response.setHeader(
"Access-Control-Allow-Origin",
"https://trusted-domain.com"
);
// Cache headers
response.setHeader("Cache-Control", "private, max-age=300");
return response.json({ data: "secure" });
}
4. Handle Errors Gracefully
export async function GET(request: Request) {
try {
const { id } = request.params;
// Database operation that might fail
const data = await database.find(id);
if (!data) {
return {
status: 404,
body: {
error: "Resource not found",
resourceId: id,
},
};
}
return { data };
} catch (error) {
console.error("Request failed:", error);
// Don't expose internal errors in production
const isProduction = process.env.NODE_ENV === "production";
return {
status: 500,
body: {
error: "Internal server error",
...(isProduction ? {} : { details: error.message }),
},
};
}
}
Common Patterns
Pagination Response
export async function GET(request: Request) {
const { page = "1", limit = "20" } = request.query;
const pageNum = parseInt(page);
const limitNum = parseInt(limit);
const offset = (pageNum - 1) * limitNum;
// Get paginated data
const [items, total] = await Promise.all([
database.items.findMany({
skip: offset,
take: limitNum,
orderBy: { createdAt: "desc" },
}),
database.items.count(),
]);
const totalPages = Math.ceil(total / limitNum);
return {
items,
pagination: {
page: pageNum,
limit: limitNum,
total,
totalPages,
hasNext: pageNum < totalPages,
hasPrev: pageNum > 1,
},
};
}
API Versioning via Headers
export async function GET(request: Request) {
const apiVersion = request.get("x-api-version") || "v1";
switch (apiVersion) {
case "v1":
return {
data: {
/* v1 data structure */
},
meta: { version: "v1" },
};
case "v2":
return {
data: {
/* v2 data structure */
},
meta: { version: "v2" },
};
default:
return {
status: 400,
body: {
error: "Unsupported API version",
supportedVersions: ["v1", "v2"],
},
};
}
}
Troubleshooting
This section covers common issues you may encounter when working with Fastay and explains why they happen and how to fix them.
Issue: request.body is undefined
In Fastay, the request body is asynchronous by design. This allows the framework to support different body parsers (JSON, form data, streams) in a consistent way.
Accessing request.body without await returns a Promise, not the actual data.
// ✗ Incorrect: returns a Promise
const body = request.body;
// ✓ Correct: resolves the request body
const body = await request.body;
Once awaited, the body contains the parsed request payload.
Issue: Cannot set headers after they are sent
This error happens when a response is sent more than once for the same request.
In Fastay, once you call response.json(), response.send(), or similar methods, the response is finalized.
Any further attempt to modify headers or send another response will throw an error.
// ✗ Sending multiple responses
response.status(401).json({ error: "Unauthorized" });
response.json({ data: "secret" }); // Error
Always stop execution after sending a response:
// ✓ Correct
response.status(401).json({ error: "Unauthorized" });
return;
This pattern is especially important in middleware.
Issue: File upload not working
Fastay distinguishes between JSON bodies and multipart form data.
For file uploads, the request must use multipart/form-data, and the body should be accessed using request.formData() instead of request.body.
const formData = await request.formData();
const file = formData.get("file") as File;
Using request.body for file uploads will not work, because multipart data requires a different parser.
Issue: Cookies not being set
Cookie behavior depends on browser security rules, not just server code.
When setting cookies in Fastay, make sure the options match your environment:
response.cookie("name", "value", {
httpOnly: true,
secure: true, // Requires HTTPS
sameSite: "none", // Required for cross-site cookies
domain: ".example.com",
});
If cookies are not being set:
- Ensure HTTPS is enabled when
secure: true - Verify
sameSitesettings for cross-origin requests - Check that the domain matches your application
If a request reaches a route handler, all registered middlewares have already completed successfully.