Project Structure in Fastay
Fastay employs a well-organized, convention-based file structure that balances simplicity with scalability. This structured approach enables rapid development while maintaining long-term maintainability as your application grows.
Understanding the Fastay Philosophy
Fastay's file structure is designed around a core principle: convention over configuration. By following established patterns, you spend less time configuring and more time building features. The structure is intuitive, with each directory serving a clear, distinct purpose that aligns with common backend development patterns.
Why Structure Matters
A well-organized project structure:
- Accelerates onboarding: New team members can quickly understand the codebase
- Improves maintainability: Related code stays together, making changes predictable
- Enhances scalability: The structure grows naturally with your application
- Facilitates testing: Organized code is easier to test in isolation
- Reduces cognitive load: Developers spend less time searching and more time coding
The Standard Fastay Structure
When you create a new Fastay project using npx fastay create-app my-app, you'll get the following organized structure:
my-app/
├── src/
│ ├── api/
│ ├── middlewares/
│ ├── services/
│ ├── utils/
│ └── index.ts
├── dist/
├── public/
├── fastay.config.json
├── package.json
└── tsconfig.json
Let's explore each component in detail.
Core Directories Explained
The src/ Directory: Your Application's Heart
The src/ (source) directory contains all your application logic. This separation from configuration files and build artifacts keeps your project clean and focused.
src/api/ - API Routes (The Foundation)
This is where Fastay's magic happens. The api/ directory uses a file-based routing system where each folder automatically becomes an API endpoint.
src/api/
├── hello/
│ └── route.ts # → GET /api/hello
├── users/
│ ├── route.ts # → GET/POST /api/users
│ └── [id]/
│ └── route.ts # → GET/PUT/DELETE /api/users/:id
└── posts/
├── route.ts # → GET/POST /api/posts
└── [id]/
├── route.ts # → GET/PUT/DELETE /api/posts/:id
└── comments/
└── route.ts # → GET/POST /api/posts/:id/comments
Key Principles of API Structure
- Each endpoint is a folder: Folders map directly to URL paths
route.tsis special: This file name is required and contains your HTTP method handlers- Square brackets for dynamics:
[id]becomes:idin the URL - Nesting creates nested routes: Folders inside folders create path hierarchies
A Complete Route Example
// src/api/users/route.ts
import { Request } from "@syntay/fastay";
// GET /api/users
export async function GET(request: Request) {
const { page, limit } = request.query;
// Business logic here
return { users: [] };
}
// POST /api/users
export async function POST(request: Request) {
const userData = await request.body;
// Create user logic
return { message: "User created" };
}
Dynamic Routes in Practice
// src/api/users/[id]/route.ts
import { Request } from "@syntay/fastay";
// GET /api/users/:id
export async function GET(request: Request) {
const { id } = request.params;
// Fetch specific user
return { user: { id, name: "John" } };
}
src/middlewares/ - Request Processing Layer
Middlewares in Fastay intercept and process requests before they reach your route handlers. This directory houses reusable logic like authentication, validation, and logging.
src/middlewares/
├── auth.ts # Authentication middleware
├── logger.ts # Request logging
├── validation.ts # Input validation
├── rateLimit.ts # Rate limiting
└── middleware.ts # Middleware configuration
The Middleware Configuration File
The middleware.ts file is where you define which middlewares apply to which routes:
// src/middlewares/middleware.ts
import { createMiddleware } from "@syntay/fastay";
import { auth } from "./auth";
import { logger } from "./logger";
import { validateUser } from "./validation";
export const middleware = createMiddleware({
// Apply to all routes
"/": [logger],
// Apply to specific routes
"/api/users": [auth, validateUser],
"/api/admin/*": [auth, adminOnly],
// Public routes (no auth)
"/api/public/*": [],
});
Creating a Custom Middleware
// src/middlewares/auth.ts
import { Request, Response, Next } from "@syntay/fastay";
export async function auth(request: Request, response: Response, next: Next) {
const token = request.headers.authorization?.split(" ")[1];
if (!token) {
return response.status(401).json({ error: "Unauthorized" });
}
// Verify token and attach user to request
try {
const user = verifyToken(token);
request.user = user;
next();
} catch (error) {
return response.status(401).json({ error: "Invalid token" });
}
}
src/services/ - Business Logic Layer
Services contain your core business logic, separated from HTTP concerns. This separation follows the Single Responsibility Principle and makes your code more testable.
src/services/
├── user-service.ts # User-related business logic
├── product-service.ts # Product-related business logic
├── email-service.ts # Email sending logic
└── payment-service.ts # Payment processing
Service Pattern Example
// src/services/user-service.ts
export class UserService {
async createUser(data: CreateUserDto) {
// Validation
if (!data.email.includes("@")) {
throw new Error("Invalid email");
}
// Business rules
if (data.age < 18) {
throw new Error("User must be at least 18 years old");
}
// Database operations
const user = await database.users.create({
data: {
email: data.email,
name: data.name,
age: data.age,
},
});
// Side effects (email, notifications, etc.)
await emailService.sendWelcomeEmail(user.email);
return user;
}
async findUsers(filters: UserFilters) {
// Complex query logic
return database.users.findMany({
where: this.buildWhereClause(filters),
include: { profile: true },
});
}
}
Using Services in Routes
// src/api/users/route.ts
import { Request } from "@syntay/fastay";
import { UserService } from "../../services/user-service";
const userService = new UserService();
export async function POST(request: Request) {
const userData = await request.body;
try {
const user = await userService.createUser(userData);
return { user };
} catch (error) {
return {
status: 400,
body: { error: error.message },
};
}
}
src/utils/ - Helper Functions and Utilities
The utils/ directory is for reusable helper functions, constants, and shared utilities that don't fit neatly into services or middlewares.
src/utils/
├── formatters.ts # Data formatting functions
├── validators.ts # Validation utilities
├── constants.ts # Application constants
├── helpers.ts # General helper functions
└── errors.ts # Custom error classes
Utility Examples
// src/utils/formatters.ts
export function formatCurrency(amount: number): string {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount);
}
export function truncateText(text: string, maxLength: number): string {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength - 3) + "...";
}
// src/utils/constants.ts
export const API_RATE_LIMIT = {
WINDOW_MS: 15 * 60 * 1000, // 15 minutes
MAX_REQUESTS: 100,
};
export const USER_ROLES = {
ADMIN: "admin",
USER: "user",
MODERATOR: "moderator",
} as const;
src/index.ts - Application Entry Point
This is your application's main file where Fastay is initialized and configured.
// src/index.ts
import { createApp } from "@syntay/fastay";
void (async () => {
await createApp({
// Server configuration
port: process.env.PORT || 5000,
// Routing configuration
apiDir: "./src/api",
baseRoute: "/api",
// Middleware configuration
middlewares: {
"/api/protected": [authMiddleware],
},
// Express configuration
expressOptions: {
middlewares: [
// Global Express middlewares
],
enableCors: {
allowAnyOrigin: process.env.NODE_ENV === "development",
},
},
});
})();
Supporting Directories
dist/ - Compiled Output
When you build your Fastay application for production, TypeScript compiles to JavaScript in this directory. This separation keeps source and build artifacts distinct.
dist/
├── api/
├── middlewares/
├── services/
├── utils/
└── index.js
Important: Never modify files in dist/ directly—they are automatically generated from your src/ files.
Boa, aqui você pegou um detalhe importante de honestidade técnica nas docs. Do jeito que estava escrito, dava a entender que o public/ é servido automaticamente, o que não é verdade no Fastay. Precisa mesmo de configuração explícita no createApp.
Vou retificar o trecho mantendo o estilo de docs profissionais e sem contradição.
public/ — Static Assets
The public/ directory contains static files such as images, CSS, JavaScript, and HTML files.
Unlike some frameworks that serve static assets by default, Fastay requires explicit configuration to enable static file serving. This design keeps the framework predictable and avoids implicit behavior.
To serve static files, configure the static option when creating the application:
import { createApp } from "@syntay/fastay";
import "dotenv/config";
const port = 5000;
void (async () => {
await createApp({
apiDir: "./src/api",
baseRoute: "/",
port,
expressOptions: {
static: {
path: "public", // Static files directory
options: {
maxAge: "1d", // Cache files for 1 day
etag: true, // Enable ETag support
},
},
},
});
})();
Once configured, all files inside the public/ directory are served from the root URL.
public/
├── images/
│ ├── logo.png
│ └── favicon.ico
├── styles/
│ └── main.css
└── index.html
Accessing Static Files
After enabling static assets, files can be accessed directly:
public/images/logo.png→http://localhost:5000/images/logo.pngpublic/index.html→http://localhost:5000/
Why Fastay Requires Explicit Static Configuration
By requiring explicit configuration for static files, Fastay ensures:
- No hidden behavior at runtime
- Full control over caching and headers
- Clear separation between API routes and static content
Configuration Files
fastay.config.json - Build Configuration
This optional configuration file lets you customize Fastay's build process:
{
"compilerOptions": {
"strict": true,
"target": "ES2020",
"module": "CommonJS",
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
package.json - Dependencies and Scripts
Your project's metadata, dependencies, and scripts:
{
"name": "my-fastay-app",
"version": "1.0.0",
"scripts": {
"dev": "fastay dev",
"dev:watch": "fastay dev:watch",
"build": "fastay build",
"start": "fastay start",
"lint": "eslint 'src/**/*.{js, jsx}' --fix",
"test": "vitest"
},
"dependencies": {
"typescript-eslint": "^8.46.4"
},
"devDependencies": {
"@types/supertest": "^6.0.3",
"@typescript-eslint/eslint-plugin": "^8.46.4",
"@typescript-eslint/parser": "^8.46.4",
"eslint": "^9.39.1",
"eslint-plugin-prettier": "^5.5.4",
"globals": "^16.5.0",
"supertest": "^7.2.2",
"typescript": "^5.9.3",
"vitest": "^4.0.17"
}
}
Advanced Structure Patterns
As your application grows, you might evolve the structure. Here are common patterns for scaling:
Feature-Based Organization
For larger applications, organize by feature rather than by technical layer:
src/
├── features/
│ ├── auth/
│ │ ├── routes.ts
│ │ ├── service.ts
│ │ └── middleware.ts
│ ├── users/
│ │ ├── routes.ts
│ │ ├── service.ts
│ │ └── types.ts
│ └── products/
│ ├── routes.ts
│ ├── service.ts
│ └── validators.ts
├── shared/
│ ├── middleware/
│ ├── utils/
│ └── types/
└── index.ts
Module-Based Organization
Similar to feature-based but with more isolation:
src/
├── modules/
│ ├── user-module/
│ │ ├── user.controller.ts
│ │ ├── user.service.ts
│ │ ├── user.repository.ts
│ │ └── user.types.ts
│ └── product-module/
│ ├── product.controller.ts
│ ├── product.service.ts
│ └── product.repository.ts
├── core/
│ ├── database/
│ ├── config/
│ └── shared/
└── app.ts
Domain-Driven Design (DDD) Structure
For complex domain logic:
src/
├── domains/
│ ├── user/
│ │ ├── entities/
│ │ ├── value-objects/
│ │ ├── aggregates/
│ │ └── repositories/
│ └── order/
│ ├── entities/
│ ├── value-objects/
│ └── services/
├── application/
│ ├── use-cases/
│ └── dto/
├── infrastructure/
│ ├── persistence/
│ └── external-services/
└── presentation/
└── api/
TypeScript Configuration
Fastay is TypeScript-first, so proper TypeScript configuration is crucial:
Recommended tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@services/*": ["src/services/*"],
"@utils/*": ["src/utils/*"],
"@middlewares/*": ["src/middlewares/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
Path Aliases for Clean Imports
With the above configuration, you can use clean import paths:
// Instead of relative paths
import { UserService } from "../../../services/user-service";
// Use path aliases
import { UserService } from "@services/user-service";
import { formatCurrency } from "@utils/formatters";
Environment Configuration
Fastay follows the standard Node.js approach to environment configuration:
.env File Structure
.env # Default environment (loaded first)
.env.local # Local overrides (gitignored)
.env.development # Development-specific
.env.production # Production-specific
.env.test # Test-specific
Environment Variable Management
// src/config/env.ts
import dotenv from "dotenv";
import path from "path";
// Load environment based on NODE_ENV
const envFile = `.env.${process.env.NODE_ENV || "development"}`;
dotenv.config({ path: path.resolve(process.cwd(), envFile) });
// Always load .env as fallback
dotenv.config({ path: path.resolve(process.cwd(), ".env") });
export const config = {
// Server
port: process.env.PORT || 5000,
nodeEnv: process.env.NODE_ENV || "development",
// Database
databaseUrl: process.env.DATABASE_URL,
// Authentication
jwtSecret: process.env.JWT_SECRET!,
jwtExpiresIn: process.env.JWT_EXPIRES_IN || "7d",
// External APIs
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
sendgridApiKey: process.env.SENDGRID_API_KEY,
// Feature flags
enableCache: process.env.ENABLE_CACHE === "true",
enableRateLimit: process.env.ENABLE_RATE_LIMIT !== "false",
// Validation
get isProduction() {
return this.nodeEnv === "production";
},
get isDevelopment() {
return this.nodeEnv === "development";
},
};
Testing Structure
Fastay doesn't enforce a testing structure, but here's a common pattern:
tests/
├── unit/
│ ├── services/
│ ├── utils/
│ └── middlewares/
├── integration/
│ ├── api/
│ └── database/
├── e2e/
│ └── api/
└── fixtures/
├── users.ts
└── products.ts
Test File Naming Convention
*.test.ts- Files that contain tests*.spec.ts- Alternative naming (same as .test.ts)test-utils.ts- Shared testing utilities
Deployment Considerations
Production Build Process
- Compile TypeScript:
npm run buildcreatesdist/directory - Install production dependencies:
npm ci --production - Set environment variables: Configure
.env.production - Run migrations: If using a database
- Start the server:
npm start
Docker Configuration
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY fastay.config.json ./
COPY tsconfig.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY src/ ./src/
# Build TypeScript
RUN npm run build
# Remove source files (optional)
RUN rm -rf src
# Expose port
EXPOSE 5000
# Start the application
CMD ["npm", "start"]
Deployment Checklist
- Update
package.jsonversion - Run tests:
npm test - Build production:
npm run build - Check environment variables
- Database migrations
- SSL certificates (for production)
- Monitoring setup
- Backup procedures
Common File Structure Anti-Patterns
✗ Avoid: Monolithic Files
// ✗ DON'T: One giant route file
// src/api/all-routes.ts (hundreds of lines)
// ✓ DO: Split by resource/feature
// src/api/users/route.ts
// src/api/products/route.ts
// src/api/orders/route.ts
✗ Avoid: Business Logic in Routes
// ✗ DON'T: Database logic in routes
export async function POST(request: Request) {
const data = await request.body;
// Direct database calls mixed with HTTP logic
const user = await prisma.user.create({ data });
// Email logic here too
await sendEmail(user.email);
return { user };
}
// ✓ DO: Use services
export async function POST(request: Request) {
const data = await request.body;
const user = await userService.createUser(data);
return { user };
}
✗ Avoid: Global State in Middleware
// ✗ DON'T: Global mutable state
let requestCount = 0; // Shared across all requests!
export function logger(request, response, next) {
requestCount++; // ✗ Race conditions!
next();
}
// ✓ DO: Use request-specific or external storage
export function logger(request, response, next) {
request.requestId = crypto.randomUUID(); // ✓ Request-specific
next();
}
Migration from Other Structures
If you're migrating from Express.js to Fastay:
Express Structure → Fastay Structure
# Express (typical)
src/
├── routes/
│ ├── users.js
│ ├── products.js
│ └── orders.js
├── controllers/
├── models/
└── app.js
# Fastay (equivalent)
src/
├── api/
│ ├── users/
│ │ └── route.ts # routes/users.js + controllers/users.js
│ ├── products/
│ │ └── route.ts
│ └── orders/
│ └── route.ts
├── services/ # Business logic (replaces some controller logic)
└── middlewares/ # Reusable middleware
Customizing the Structure
While Fastay provides a recommended structure, you can customize it to fit your needs:
Changing the API Directory
// src/index.ts
await createApp({
apiDir: "./src/routes", // Custom directory
baseRoute: "/api/v1",
});
Best Practices Summary
- Follow conventions: Stick to Fastay's default structure when starting
- Keep routes thin: Delegate business logic to services
- Use TypeScript features: Interfaces, types, and path aliases
- Organize by feature: As you scale, consider feature-based organization
- Separate concerns: Clear separation between HTTP, business logic, and data access
- Consistent naming: Use consistent file and directory naming
- Environment-aware: Different configurations for dev/test/prod
- Document structure: Keep a STRUCTURE.md file for large teams
Fastay's file structure is designed to grow with your application. Start with the simple conventions, and evolve the structure as your needs become more complex. The framework provides the right balance of guidance and flexibility to keep your project organized from prototype to production.