How to build a RESTful API with Edge Functions and Edge SQL
This guide demonstrates how to build a complete RESTful API for task management using Azion Edge Functions and Edge SQL. You’ll learn to implement full CRUD (Create, Read, Update, Delete) operations at the edge, providing fast, scalable API endpoints globally.
Requirements
Before you begin, ensure you have:
- An Azion account
- Azion CLI installed and configured
- Node.js version 18 or higher
- pnpm package manager installed
- Edge SQL enabled in your Azion account
- Basic understanding of JavaScript, REST APIs, and SQL
- An Edge SQL instance configured and running
Getting started
Step 1: Set up your development environment
- Clone the RESTful tasks example repository:
git clone https://github.com/egermano/edge-functions-examples.gitcd edge-functions-examples/packages/restful-tasks
- Install the project dependencies:
pnpm install
- Review the project structure to understand the implementation:
ls -la
You should see the main files including the Edge Function implementation, SQL schema, and configuration files.
Step 2: Configure Edge SQL connection
- Create a
.env
file based on the example:
cp .env.example .env
- Edit the
.env
file to include your Edge SQL configuration:
# Edge SQL ConfigurationEDGE_SQL_CONNECTION_STRING=your_edge_sql_connection_stringDATABASE_NAME=tasks_db
- Get your Edge SQL connection details from Azion Console > Edge SQL.
Step 3: Set up the database schema
- Connect to your Edge SQL instance and create the tasks table:
CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, completed BOOLEAN DEFAULT FALSE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP);
- Optionally, insert some sample data for testing:
INSERT INTO tasks (title, completed) VALUES ('Complete API documentation', FALSE), ('Test edge function deployment', FALSE), ('Review security configurations', TRUE);
Step 4: Build the project
Compile your Edge Function for deployment:
pnpm build
This command builds your Edge Function and prepares it for deployment to Azion’s edge network.
Step 5: Test locally
Before deploying, test your API locally:
pnpm dev
This starts a local development server where you can test all API endpoints.
API endpoints
The RESTful Tasks API provides the following endpoints:
GET /tasks
Retrieve all tasks from the database.
Response:
{ "tasks": [ { "id": 1, "title": "Complete API documentation", "completed": false, "created_at": "2023-01-01T10:00:00Z", "updated_at": "2023-01-01T10:00:00Z" } ]}
GET /tasks/
Retrieve a specific task by ID.
Response:
{ "task": { "id": 1, "title": "Complete API documentation", "completed": false, "created_at": "2023-01-01T10:00:00Z", "updated_at": "2023-01-01T10:00:00Z" }}
POST /tasks
Create a new task.
Request body:
{ "title": "New task title", "completed": false}
Response:
{ "task": { "id": 4, "title": "New task title", "completed": false, "created_at": "2023-01-01T12:00:00Z", "updated_at": "2023-01-01T12:00:00Z" }}
PUT /tasks/
Update an existing task.
Request body:
{ "title": "Updated task title", "completed": true}
Response:
{ "task": { "id": 1, "title": "Updated task title", "completed": true, "created_at": "2023-01-01T10:00:00Z", "updated_at": "2023-01-01T14:00:00Z" }}
DELETE /tasks/
Delete a task by ID.
Response:
{ "message": "Task deleted successfully"}
Deploying to Azion
Step 1: Authenticate with Azion
- Log in to your Azion account via CLI:
azion login
- Follow the authentication prompts to connect your CLI with your Azion account.
Step 2: Configure Edge SQL connection
- Create secrets for your database connection:
azion create secret EDGE_SQL_CONNECTION_STRING
- When prompted, enter your Edge SQL connection string.
Step 3: Deploy the Edge Function
Deploy your RESTful API to Azion’s edge network:
azion deploy
The deployment process will:
- Upload your Edge Function code
- Configure the edge application
- Set up routing rules for all API endpoints
- Configure database connections
- Provide you with a unique domain
Step 4: Access your API
After deployment, you’ll receive a domain like https://xxxxxxx.map.azionedge.net
. Your RESTful API will be available at this URL within a few minutes after DNS propagation.
Understanding the implementation
Edge Function structure
The RESTful API Edge Function includes:
- Router setup: Handling different HTTP methods and routes
- Database connection: Connecting to Edge SQL
- CRUD operations: Implementing create, read, update, delete operations
- Error handling: Managing database and request errors
- Response formatting: Returning consistent JSON responses
Database operations example
Here’s how database operations are implemented:
// Get all tasksasync function getAllTasks() { const query = 'SELECT * FROM tasks ORDER BY created_at DESC'; const result = await database.query(query); return result.rows;}
// Create a new taskasync function createTask(title, completed = false) { const query = ` INSERT INTO tasks (title, completed) VALUES (?, ?) RETURNING * `; const result = await database.query(query, [title, completed]); return result.rows[0];}
// Update a taskasync function updateTask(id, updates) { const { title, completed } = updates; const query = ` UPDATE tasks SET title = ?, completed = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? RETURNING * `; const result = await database.query(query, [title, completed, id]); return result.rows[0];}
Key benefits
- Performance: API processing at the edge reduces latency
- Scalability: Automatically scales with demand
- Global distribution: Edge locations provide worldwide coverage
- Data consistency: Edge SQL ensures ACID compliance
- Cost efficiency: Optimized data access and transfer
Testing your RESTful API
Step 1: Test CRUD operations
- Create tasks: Test POST /tasks endpoint
- Read tasks: Test GET /tasks and GET /tasks/ endpoints
- Update tasks: Test PUT /tasks/ endpoint
- Delete tasks: Test DELETE /tasks/ endpoint
Step 2: Test error handling
- Invalid data: Test with malformed request bodies
- Non-existent resources: Test with invalid task IDs
- Database errors: Test error handling for connection issues
- Validation errors: Test input validation logic
Step 3: Performance testing
- Response times: Measure API response times
- Concurrent requests: Test multiple simultaneous requests
- Database performance: Monitor SQL query performance
- Load testing: Test with high request volumes
Example API usage
Using cURL
# Get all taskscurl -X GET https://your-domain.map.azionedge.net/tasks
# Create a new taskcurl -X POST https://your-domain.map.azionedge.net/tasks \ -H "Content-Type: application/json" \ -d '{"title": "Learn Azion Edge Functions", "completed": false}'
# Update a taskcurl -X PUT https://your-domain.map.azionedge.net/tasks/1 \ -H "Content-Type: application/json" \ -d '{"title": "Updated task title", "completed": true}'
# Delete a taskcurl -X DELETE https://your-domain.map.azionedge.net/tasks/1
Using JavaScript fetch
// Get all tasksconst tasks = await fetch('/tasks').then(r => r.json());
// Create a new taskconst newTask = await fetch('/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: 'New task', completed: false })}).then(r => r.json());
// Update a taskconst updatedTask = await fetch('/tasks/1', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: 'Updated task', completed: true })}).then(r => r.json());
Advanced features
Adding authentication
Implement authentication to secure your API:
async function authenticateRequest(request) { const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new Error('Authentication required'); }
const token = authHeader.substring(7); return await validateToken(token);}
Input validation
Add comprehensive input validation:
function validateTaskData(data) { const errors = [];
if (!data.title || typeof data.title !== 'string') { errors.push('Title is required and must be a string'); }
if (data.title && data.title.length > 255) { errors.push('Title must be less than 255 characters'); }
if (data.completed !== undefined && typeof data.completed !== 'boolean') { errors.push('Completed must be a boolean'); }
return errors;}
Pagination
Implement pagination for large datasets:
async function getTasksPaginated(page = 1, limit = 10) { const offset = (page - 1) * limit; const query = ` SELECT * FROM tasks ORDER BY created_at DESC LIMIT ? OFFSET ? `; const tasks = await database.query(query, [limit, offset]);
const countQuery = 'SELECT COUNT(*) as total FROM tasks'; const totalResult = await database.query(countQuery); const total = totalResult.rows[0].total;
return { tasks: tasks.rows, pagination: { page, limit, total, totalPages: Math.ceil(total / limit) } };}
Troubleshooting
Common issues and solutions
- Database connection errors: Verify Edge SQL credentials and connection string
- API endpoint not found: Check routing configuration and deployment
- SQL errors: Validate database schema and query syntax
- Performance issues: Optimize SQL queries and database indexing
Debugging tips
- Enable logging: Add comprehensive logging for debugging
- Test locally: Use local development server to isolate issues
- Monitor performance: Track API response times and database queries
- Error handling: Implement proper error responses and status codes
Next steps
- Implement advanced filtering and sorting
- Add real-time updates with WebSockets
- Integrate with authentication providers
- Implement API rate limiting and caching
- Add comprehensive API documentation with OpenAPI/Swagger