RESTful API Example
Learn how to build a complete RESTful API with Azura.
This example demonstrates how to create a RESTful API for a blog with posts and comments. It includes CRUD operations, validation, error handling, and more.
Project Structure
Let's start by setting up the project structure:
blog-api/
├── src/
│ ├── controllers/
│ │ ├── post.controller.ts
│ │ └── comment.controller.ts
│ ├── models/
│ │ ├── post.model.ts
│ │ └── comment.model.ts
│ ├── services/
│ │ ├── post.service.ts
│ │ └── comment.service.ts
│ ├── middleware/
│ │ ├── error.middleware.ts
│ │ └── validation.middleware.ts
│ ├── config/
│ │ └── database.ts
│ └── index.ts
├── package.json
└── tsconfig.json
Setup
First, let's set up the project and install the necessary dependencies:
package.json
npm init -y
npm install @atosjs/azura
npm install --save-dev typescript ts-node @types/node
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"]
}
Models
Let's define the data models for our blog API:
post.model.ts
// src/models/post.model.ts
export interface Post {
id: string;
title: string;
content: string;
author: string;
createdAt: Date;
updatedAt: Date;
}
export interface CreatePostDto {
title: string;
content: string;
author: string;
}
export interface UpdatePostDto {
title?: string;
content?: string;
}
comment.model.ts
// src/models/comment.model.ts
export interface Comment {
id: string;
postId: string;
content: string;
author: string;
createdAt: Date;
}
export interface CreateCommentDto {
postId: string;
content: string;
author: string;
}
Services
Now, let's create the services that will handle the business logic:
post.service.ts
// src/services/post.service.ts
import { Post, CreatePostDto, UpdatePostDto } from '../models/post.model';
// In-memory database for this example
const posts: Post[] = [];
export class PostService {
getAllPosts(): Post[] {
return posts;
}
getPostById(id: string): Post | undefined {
return posts.find(post => post.id === id);
}
createPost(createPostDto: CreatePostDto): Post {
const newPost: Post = {
id: Date.now().toString(),
...createPostDto,
createdAt: new Date(),
updatedAt: new Date()
};
posts.push(newPost);
return newPost;
}
updatePost(id: string, updatePostDto: UpdatePostDto): Post | undefined {
const postIndex = posts.findIndex(post => post.id === id);
if (postIndex === -1) {
return undefined;
}
const updatedPost = {
...posts[postIndex],
...updatePostDto,
updatedAt: new Date()
};
posts[postIndex] = updatedPost;
return updatedPost;
}
deletePost(id: string): boolean {
const initialLength = posts.length;
const newPosts = posts.filter(post => post.id !== id);
if (newPosts.length === initialLength) {
return false;
}
// Update the posts array
posts.length = 0;
posts.push(...newPosts);
return true;
}
}
comment.service.ts
// src/services/comment.service.ts
import { Comment, CreateCommentDto } from '../models/comment.model';
// In-memory database for this example
const comments: Comment[] = [];
export class CommentService {
getCommentsByPostId(postId: string): Comment[] {
return comments.filter(comment => comment.postId === postId);
}
createComment(createCommentDto: CreateCommentDto): Comment {
const newComment: Comment = {
id: Date.now().toString(),
...createCommentDto,
createdAt: new Date()
};
comments.push(newComment);
return newComment;
}
deleteComment(id: string): boolean {
const initialLength = comments.length;
const newComments = comments.filter(comment => comment.id !== id);
if (newComments.length === initialLength) {
return false;
}
// Update the comments array
comments.length = 0;
comments.push(...newComments);
return true;
}
}
Controllers
Now, let's create the controllers that will handle the HTTP requests:
post.controller.ts
// src/controllers/post.controller.ts
import { Controller, Get, Post, Put, Delete, Body, Param } from '@atosjs/azura';
import { PostService } from '../services/post.service';
import { CreatePostDto, UpdatePostDto } from '../models/post.model';
import { HttpError } from '@atosjs/azura';
@Controller('/posts')
export class PostController {
private postService: PostService;
constructor() {
this.postService = new PostService();
}
@Get('/')
getAllPosts() {
return this.postService.getAllPosts();
}
@Get('/:id')
getPostById(@Param('id') id: string) {
const post = this.postService.getPostById(id);
if (!post) {
throw new HttpError(404, { message: 'Post not found' });
}
return post;
}
@Post('/')
createPost(@Body() createPostDto: CreatePostDto) {
// Validate input
if (!createPostDto.title || !createPostDto.content || !createPostDto.author) {
throw new HttpError(400, { message: 'Title, content, and author are required' });
}
return this.postService.createPost(createPostDto);
}
@Put('/:id')
updatePost(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) {
// Validate input
if (Object.keys(updatePostDto).length === 0) {
throw new HttpError(400, { message: 'No update data provided' });
}
const updatedPost = this.postService.updatePost(id, updatePostDto);
if (!updatedPost) {
throw new HttpError(404, { message: 'Post not found' });
}
return updatedPost;
}
@Delete('/:id')
deletePost(@Param('id') id: string) {
const deleted = this.postService.deletePost(id);
if (!deleted) {
throw new HttpError(404, { message: 'Post not found' });
}
return { message: 'Post deleted successfully' };
}
}
comment.controller.ts
// src/controllers/comment.controller.ts
import { Controller, Get, Post, Delete, Body, Param } from '@atosjs/azura';
import { CommentService } from '../services/comment.service';
import { PostService } from '../services/post.service';
import { CreateCommentDto } from '../models/comment.model';
import { HttpError } from '@atosjs/azura';
@Controller('/comments')
export class CommentController {
private commentService: CommentService;
private postService: PostService;
constructor() {
this.commentService = new CommentService();
this.postService = new PostService();
}
@Get('/post/:postId')
getCommentsByPostId(@Param('postId') postId: string) {
// Check if post exists
const post = this.postService.getPostById(postId);
if (!post) {
throw new HttpError(404, { message: 'Post not found' });
}
return this.commentService.getCommentsByPostId(postId);
}
@Post('/')
createComment(@Body() createCommentDto: CreateCommentDto) {
// Validate input
if (!createCommentDto.postId || !createCommentDto.content || !createCommentDto.author) {
throw new HttpError(400, { message: 'PostId, content, and author are required' });
}
// Check if post exists
const post = this.postService.getPostById(createCommentDto.postId);
if (!post) {
throw new HttpError(404, { message: 'Post not found' });
}
return this.commentService.createComment(createCommentDto);
}
@Delete('/:id')
deleteComment(@Param('id') id: string) {
const deleted = this.commentService.deleteComment(id);
if (!deleted) {
throw new HttpError(404, { message: 'Comment not found' });
}
return { message: 'Comment deleted successfully' };
}
}
Middleware
Let's create some middleware for error handling and request logging:
error.middleware.ts
// src/middleware/error.middleware.ts
import { Request, Response, NextFunction } from '@atosjs/azura';
import { HttpError } from '@atosjs/azura';
export function errorMiddleware(err: Error, req: Request, res: Response, next: NextFunction) {
console.error('Error:', err);
if (err instanceof HttpError) {
return res.status(err.status).json(err.payload);
}
// Default error response for unhandled errors
return res.status(500).json({
message: 'Internal Server Error',
error: process.env.NODE_ENV === 'production' ? undefined : err.message
});
}
logging.middleware.ts
// src/middleware/logging.middleware.ts
import { Request, Response, NextFunction } from '@atosjs/azura';
export function loggingMiddleware(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
// Add a listener for when the response finishes
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.path} - ${res.statusCode} (${duration}ms)`);
});
next();
}
Main Application File
Finally, let's create the main application file that ties everything together:
index.ts
// src/index.ts
import { AzuraServer } from '@atosjs/azura';
import { PostController } from './controllers/post.controller';
import { CommentController } from './controllers/comment.controller';
import { errorMiddleware } from './middleware/error.middleware';
import { loggingMiddleware } from './middleware/logging.middleware';
async function bootstrap() {
const app = new AzuraServer({
port: process.env.PORT ? parseInt(process.env.PORT) : 3000
});
// Register middleware
app.use(loggingMiddleware);
// Register controllers
app.load([
PostController,
CommentController
]);
// Register error middleware (should be last)
app.use(errorMiddleware);
// Start the server
app.listen();
console.log(`Server is running on http://localhost:${app.config.port}`);
}
bootstrap().catch(console.error);
Testing the API
Now that we have our API set up, let's test it with some HTTP requests:
Create a Post
# Create a post
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-d '{
"title": "Hello Azura",
"content": "This is my first post with Azura.JS Framework",
"author": "John Doe"
}'
Get All Posts
# Get all posts
curl http://localhost:3000/posts
Get a Specific Post
# Get a specific post
curl http://localhost:3000/posts/1234567890
Update a Post
# Update a post
curl -X PUT http://localhost:3000/posts/1234567890 \
-H "Content-Type: application/json" \
-d '{
"title": "Updated Title",
"content": "Updated content for my post"
}'
Delete a Post
# Delete a post
curl -X DELETE http://localhost:3000/posts/1234567890
Create a Comment
# Create a comment
curl -X POST http://localhost:3000/comments \
-H "Content-Type: application/json" \
-d '{
"postId": "1234567890",
"content": "Great post!",
"author": "Jane Smith"
}'
Get Comments for a Post
# Get comments for a post
curl http://localhost:3000/comments/post/1234567890
Conclusion
In this example, we've built a complete RESTful API for a blog with posts and comments. We've covered:
- Setting up the project structure
- Defining data models and DTOs
- Creating services for business logic
- Implementing controllers with decorators
- Adding middleware for error handling and logging
- Testing the API with HTTP requests