Backend API for the Takeoff event registration system built with Express.js, Prisma, and PostgreSQL.
- Development:
http://localhost:4500 - Production:
https://takeoff.opensourcenest.org(TBD)
Register a new participant for the event.
Endpoint: POST /api/events/register
Request Body:
{
"firstName": "John",
"lastName": "Doe",
"email": "user@example.com",
"isCommunityMember": true,
"communityDetails": "Open Source Nest",
"profession": ["FULLSTACK_DEVELOPER"],
"professionOther": null,
"location": "Lagos, Nigeria",
"locationOther": null,
"referralSource": "SOCIAL_MEDIA",
"newsletterSub": true,
"pipelineInterest": "YES",
"interests": "React, TypeScript, AI",
"openSourceKnowledge": 8
}Success Response (201):
{
"success": true,
"data": {
"id": "clxyz123...",
"firstName": "John",
"lastName": "Doe",
"email": "user@example.com",
"isCommunityMember": true,
"profession": ["FULLSTACK_DEVELOPER"],
"professionOther": null,
"location": "Lagos, Nigeria",
"locationOther": null,
"referralSource": "SOCIAL_MEDIA",
"openSourceKnowledge": 8,
"createdAt": "2026-01-14T12:00:00.000Z"
}
}Error Responses:
- 400 Bad Request - Validation error
{
"success": false,
"error": "Invalid email format",
"validationErrors": [
{
"code": "invalid_string",
"message": "Invalid email format",
"path": ["email"]
}
]
}- 409 Conflict - Email already registered
{
"success": false,
"error": "This email address is already registered."
}Retrieve all event registrations.
Endpoint: GET /api/events/registrations
Success Response (200):
{
"success": true,
"data": [
{
"id": "clxyz123...",
"firstName": "John",
"lastName": "Doe",
"email": "user@example.com",
"isCommunityMember": true,
"role": "FULLSTACK_DEVELOPER",
"roleOther": null,
"location": "Lagos, Nigeria",
"locationOther": null,
"openSourceKnowledge": 8,
"createdAt": "2026-01-14T12:00:00.000Z"
}
]
}Retrieve a specific registration by ID.
Endpoint: GET /api/events/registrations/:id
Success Response (200):
{
"success": true,
"data": {
"id": "clxyz123...",
"firstName": "John",
"lastName": "Doe",
"email": "user@example.com",
...
}
}Error Response (404):
{
"success": false,
"error": "Registration not found."
}Update an existing registration.
Endpoint: PUT /api/events/registrations/:id
Request Body (all fields optional):
{
"firstName": "Updated Name",
"openSourceKnowledge": 9
}Success Response (200):
{
"success": true,
"data": {
"id": "clxyz123...",
"firstName": "Updated Name",
...
}
}Total: 32 roles
PROFESSIONAL_DEVELOPERHOBBYIST
FRONTEND_DEVELOPERBACKEND_DEVELOPERFULLSTACK_DEVELOPERDEVOPS_ENGINEERQA_ENGINEERSECURITY_ENGINEER
DATA_SCIENTISTAI_ML_ENGINEER
UI_UX_DESIGNERPRODUCT_MANAGERPROJECT_MANAGER
SMART_CONTRACT_DEVELOPERBLOCKCHAIN_DEVELOPERWEB3_DEVELOPERSOLIDITY_DEVELOPERDAPP_DEVELOPERTOKENOMICS_SPECIALISTNFT_DEVELOPERDEFI_DEVELOPERWEB3_SECURITY_AUDITORBLOCKCHAIN_ARCHITECT
TECHNICAL_WRITERCONTENT_CREATORCOMMUNITY_MANAGER
FOUNDERIT_SUPPORTBUSINESS_ANALYST
STUDENTEDUCATOR
OTHER- UseprofessionOtherfield to specify
AI_ML_ENGINEER
BACKEND_DEVELOPER
BLOCKCHAIN_ARCHITECT
BLOCKCHAIN_DEVELOPER
BUSINESS_ANALYST
COMMUNITY_MANAGER
CONTENT_CREATOR
DATA_SCIENTIST
DAPP_DEVELOPER
DEFI_DEVELOPER
DEVOPS_ENGINEER
EDUCATOR
FOUNDER
FRONTEND_DEVELOPER
FULLSTACK_DEVELOPER
HOBBYIST
IT_SUPPORT
NFT_DEVELOPER
OTHER
PRODUCT_MANAGER
PROFESSIONAL_DEVELOPER
PROJECT_MANAGER
QA_ENGINEER
SECURITY_ENGINEER
SMART_CONTRACT_DEVELOPER
SOLIDITY_DEVELOPER
STUDENT
TECHNICAL_WRITER
TOKENOMICS_SPECIALIST
UI_UX_DESIGNER
WEB3_DEVELOPER
WEB3_SECURITY_AUDITOR
firstName- min 1 character (trimmed)lastName- min 1 character (trimmed)email- valid email format (auto-lowercased)isCommunityMember- booleanprofession- must be one of the valid professions abovelocation- any stringopenSourceKnowledge- number between 1-10
professionOther- string (required if profession is "OTHER")locationOther- string (for custom locations)communityDetails- stringreferralSource- enumnewsletterSub- booleanpipelineInterest- enuminterests- string
email→ trimmed and lowercasedfirstName→ trimmedlastName→ trimmedopenSourceKnowledge→ coerced to number
{
firstName: string; // Required, min 1 char
lastName: string; // Required, min 1 char
email: string; // Required, valid email
isCommunityMember: boolean; // Required
profession: Profession[]; // Required, enum array
professionOther: string | null; // Optional
location: string; // Required
locationOther: string | null;// Optional
referralSource: ReferralSource; // Optional
newsletterSub: boolean; // Optional
pipelineInterest: PipelineInterest; // Optional
interests: string | null; // Optional
openSourceKnowledge: number; // Required, 1-10
}Upon successful registration, a welcome email is automatically sent to the registrant's email address.
Email Template:
- Subject: "Welcome to Takeoff Event!"
- Content: Personalized welcome message with registrant's first name
curl -X POST http://localhost:4500/api/events/register \
-H "Content-Type: application/json" \
-d '{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"isCommunityMember": true,
"profession": "FRONTEND_DEVELOPER",
"professionOther": null,
"location": "Remote",
"locationOther": null,
"openSourceKnowledge": 7
}'const response = await fetch('http://localhost:4500/api/events/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
isCommunityMember: true,
profession: 'FRONTEND_DEVELOPER',
professionOther: null,
location: 'Remote',
locationOther: null,
openSourceKnowledge: 7,
}),
});
const data = await response.json();
console.log(data);- Node.js 18+
- PostgreSQL database
- npm or yarn
Create a .env file:
PORT=4500
DATABASE_URL="postgresql://user:password@host:5432/database?sslmode=require"
SMTP_HOST=smtp.gmail.com
SMTP_PORT=465
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
SMTP_TIMEOUT=60000
# CORS Configuration (Required in Production)
ALLOWED_ORIGINS="https://your-frontend-domain.com,https://another-domain.com"# Install dependencies
npm install
# Generate Prisma Client
npx prisma generate
# Run database migrations
npx prisma db push
# Start development server
npm run devServer will start on http://localhost:4500
This project follows a 3-layer architecture for better separation of concerns and testability:
- Controller Layer (
src/controllers): Handles HTTP requests, validation, and responses. Lean and focused. - Service Layer (
src/services): Contains business logic and handles database interactions using Prisma. - Data Access Layer: Prisma Client acting as the ORM.
It also features Global Error Handling via middleware to ensure consistent API responses.
- Runtime: Node.js + Express.js
- Database: PostgreSQL + Prisma ORM
- Validation: Zod
- Email: Nodemailer
- Language: TypeScript
- Testing: Vitest + Supertest
We welcome contributions! Please read our CONTRIBUTING.md guide to get started.
To run tests locally:
npm testtakeoff-backend/
├── src/
│ ├── controllers/
│ │ └── eventController.ts # Request handlers (lean)
│ ├── services/
│ │ └── eventService.ts # Business logic & DB calls
│ ├── routes/
│ │ └── eventRoutes.ts # API routes
│ ├── schemas/
│ │ └── event.schema.ts # Zod validation schemas
│ ├── middleware/
│ │ └── errorHandler.ts # Global error handling
│ ├── lib/
│ │ └── prisma.ts # Prisma client
│ └── utils/
│ ├── AppError.ts # Custom error class
│ └── asyncHandler.ts # Async wrapper
├── prisma/
│ └── schema.prisma # Database schema
├── src/tests/ # Integration tests
├── app.ts # Main app file
└── .env # Environment variables
- Check SMTP credentials in
.env - Ensure Gmail "App Passwords" is used (not regular password)
- Check server logs for email errors
- Ensure all required fields are provided
- Verify
professionis exactly one of the valid enum values (case-sensitive) - Check
openSourceKnowledgeis between 1-10
- Development: Allowed origins are
localhost:4500,localhost:3001,localhost:5173,localhost:8080(unless overridden by.env). - Production: You MUST set
ALLOWED_ORIGINSin your environment variables (comma-separated URLs).
For questions or issues, contact the backend team or create an issue in the repository.
Repository: https://github.com/OpenSourceNest/takeoff-backend