DEV Community

Cover image for Building a Contact Form Backend with FastAPI and Discord Integration
Vicente G. Reyes
Vicente G. Reyes

Posted on • Originally published at blog.vicentereyes.org

Building a Contact Form Backend with FastAPI and Discord Integration

In this tutorial, we'll build a secure backend API using FastAPI that handles contact form submissions and forwards them to a Discord channel using webhooks. We'll also cover how to properly set up CORS to allow requests from specific domains.

Prerequisites

  • Python 3.11+
  • FastAPI
  • httpx (for making async HTTP requests)
  • A Discord webhook URL

Step 1: Setting Up the Project

First, create a new directory for your project and install the required dependencies:

pip install fastapi uvicorn httpx python-dotenv
Enter fullscreen mode Exit fullscreen mode

Step 2: Creating the FastAPI Application

Create a new file called main.py:

import os
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import httpx

app = FastAPI()

# Configure CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://vicentereyes.org",
        "https://www.vicentereyes.org"
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
Enter fullscreen mode Exit fullscreen mode

Step 3: Define the Data Model

We'll use Pydantic to define our form data structure:

class FormData(BaseModel):
    name: str
    email: str
    message: str
    service: str
    companyName: str
    companyUrl: str
Enter fullscreen mode Exit fullscreen mode

Step 4: Create the Submission Endpoint

Add the endpoint that handles form submissions:

@app.post("/submit/")
@app.post("/submit")  # Handle both with and without trailing slash
async def submit_form(form_data: FormData):
    try:
        # Format the message for Discord
        message_content = {
            "content": f"New form submission: \n"
                      f"**Name:** {form_data.name}\n"
                      f"**Email:** {form_data.email}\n"
                      f"**Message:** {form_data.message}\n"
                      f"**Service:** {form_data.service}\n"
                      f"**Company Name:** {form_data.companyName}\n"
                      f"**Company URL:** {form_data.companyUrl}"
        }

        # Send to Discord webhook
        async with httpx.AsyncClient() as client:
            response = await client.post(DISCORD_WEBHOOK_URL, json=message_content)

        if response.status_code != 204:
            raise HTTPException(status_code=response.status_code, 
                              detail="Failed to send message to Discord")

        return {"message": "Form data sent to Discord successfully"}

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
Enter fullscreen mode Exit fullscreen mode

Step 5: Environment Configuration

Create a .env file to store your Discord webhook URL:

FASTAPI_DISCORD_WEBHOOK_URL=your_discord_webhook_url_here
Enter fullscreen mode Exit fullscreen mode

How It Works

  1. CORS Configuration:

    • The CORSMiddleware is configured to only accept requests from specified domains
    • This is crucial for security as it prevents unauthorized domains from accessing your API
    • The middleware is set up to allow all HTTP methods and headers while maintaining origin restrictions
  2. Data Validation:

    • The FormData Pydantic model ensures that all incoming data matches the expected structure
    • If any required fields are missing or have incorrect types, FastAPI automatically returns a validation error
  3. Discord Integration:

    • When a form is submitted, the data is formatted into a Discord message
    • The message is sent to a Discord channel using a webhook URL
    • We use httpx for making async HTTP requests to Discord's API
  4. Error Handling:

    • The endpoint is wrapped in a try-catch block to handle potential errors
    • If Discord's webhook fails, we return an appropriate HTTP error
    • Any other exceptions are caught and returned as 500 Internal Server errors

Running the Application

Start the server with:

uvicorn main:app --reload
Enter fullscreen mode Exit fullscreen mode

The API will be available at http://localhost:8000.

Security Considerations

  1. CORS: Only allow domains that actually need to access your API
  2. Environment Variables: Keep sensitive data like webhook URLs in environment variables
  3. Input Validation: Use Pydantic models to validate all incoming data
  4. Error Handling: Never expose internal error details to clients

Frontend Integration

To use this API from your frontend, make sure your requests include the correct headers and match the expected data structure:

const response = await fetch('your_api_url/submit', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'John Doe',
    email: 'john@example.com',
    message: 'Hello!',
    service: 'Consulting',
    companyName: 'Example Corp',
    companyUrl: 'https://example.com'
  })
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

This setup provides a secure and efficient way to handle contact form submissions and forward them to Discord. The use of FastAPI makes the code clean and maintainable, while proper CORS configuration ensures security. The async nature of the application means it can handle multiple submissions efficiently without blocking.

Code: https://github.com/reyesvicente/Portfolio-Contact-Form-Backend
Live: https://vicentereyes.org/contact

Top comments (0)