A full-stack social messaging/feed application built while modernizing an older React project into a TypeScript + Express + Prisma backend.
The project is currently focused on building a clean REST API with proper service/controller separation, validation, file uploads, database access, and centralized error handling.
In active development.
Current backend work includes:
- TypeScript backend
- Express server
- Prisma ORM
- MySQL database
- Post model
- User/Post relationship
- Get all posts
- Get a single post
- Create a post
- Update a post
- Image upload with Multer
- Static image serving
- Image cleanup when replacing images
- Custom
AppError - Centralized Express error handling
- Zod-based request validation setup
- Delete post
- Authentication/authorization
- User ownership checks
- Production-ready image storage
- Complete API documentation
- Frontend migration/modernization
- React
- React Router
- CSS
- Fetch API
The frontend originated from an older React application and is being progressively adapted to communicate with the new backend.
- Node.js
- Express
- TypeScript
- Prisma
- MySQL
- Zod
- Multer
The backend follows a layered structure:
Request
│
▼
Express Route
│
▼
Controller
│
▼
Service
│
▼
Prisma
│
▼
MySQL
Controllers are responsible primarily for HTTP concerns, while services contain application/database logic.
The backend is organized by feature rather than putting all controllers and services into global folders.
Example:
src/
├── errors/
│ └── AppError.ts
│
├── lib/
│ └── prisma.ts
│
├── utils/
│ ├── path.ts
│ └── deleteImage.ts
│
├── modules/
│ └── posts/
│ ├── postController.ts
│ ├── postService.ts
│ └── postSchema.ts
│
├── app.ts
└── server.ts
The exact folder names may evolve as the project continues to grow.
Posts currently contain information such as:
Post
├── id
├── title
├── content
├── imageUrl
├── creatorId
└── createdAt
Posts have a relationship with a User through creatorId.
The current API is centered around posts.
GET /postsReturns the posts ordered by creation date, along with creator information.
Example response:
{
"posts": [
{
"id": 1,
"title": "Hello World",
"content": "My first post",
"imageUrl": "/images/example.jpg",
"creator": {
"name": "User"
}
}
],
"totalItems": 1
}GET /posts/:postIdReturns a single post and its creator.
If the post doesn't exist:
404 Not FoundPOST /postsThe endpoint accepts multipart form data because posts can contain an image.
Example fields:
title
content
image
The image is processed using Multer and stored on the server.
PUT /posts/:postIdThe update endpoint currently accepts:
title
content
image
When a new image is uploaded:
New image
↓
Multer saves new file
↓
Database updated with new image URL
↓
Old image deleted
The old image is retrieved from the existing database record rather than trusting a client-provided path.
Multer handles uploaded images.
Uploaded images are served through Express static middleware:
app.use("/images", express.static(...));Images are referenced by URLs such as:
/images/1723456789-example.jpg
When replacing an existing post image, the backend:
- Finds the existing post.
- Stores the old image URL.
- Updates the database.
- Deletes the old image from disk.
- Returns the updated post.
The database update happens before deleting the old image to avoid deleting the existing image if the database update fails.
The backend uses a custom AppError class for application-level errors.
Example:
throw new AppError("Couldn't find the post.", 404);Controllers forward errors to Express:
catch (err) {
next(err);
}A centralized error-handling middleware is responsible for converting errors into HTTP responses.
This keeps controllers from having to duplicate error-response logic.
Request validation is being implemented using Zod.
Schemas define the expected structure of incoming data rather than relying solely on TypeScript types.
For example:
const createPostSchema = z.object({
title: z.string(),
content: z.string()
});TypeScript types can then be inferred from schemas:
type CreatePostInput = z.infer<typeof createPostSchema>;This provides both runtime validation and compile-time type safety.
The project uses Prisma as its ORM with MySQL.
Prisma is responsible for:
- Querying posts
- Creating posts
- Updating posts
- Counting posts
- Loading relationships
- Selecting only required fields
For example, when checking whether a post exists, only the fields required by the operation can be selected:
const post = await prisma.post.findUnique({
where: {
id: postId
},
select: {
id: true,
imageUrl: true
}
});This avoids unnecessarily retrieving the entire record.
Database operations are kept inside services instead of directly inside controllers.
For example:
postController
│
▼
updatePostService()
│
├── validate operation
├── find existing post
├── update database
└── clean up old image
This makes the controller primarily responsible for translating HTTP requests into service calls.
Install dependencies:
npm installGenerate the Prisma client:
npx prisma generateRun the development server according to the project's configured npm scripts.
Sensitive configuration should be stored in environment variables rather than committed to Git.
Typical configuration includes:
DATABASE_URL="mysql://USER:PASSWORD@HOST:PORT/DATABASE"
PORT=8080Do not commit .env files containing real credentials.
- Complete CRUD operations
- Authentication
- Password hashing
- JWT/session strategy
- Authorization
- Post ownership checks
- Better request validation
- Pagination
- Filtering/search
- Rate limiting
- Production logging
- Automated tests
- API documentation
The current implementation stores images locally.
Future production storage can use:
- Amazon S3
- Cloudflare R2
- Cloudinary
This would allow the application to scale beyond a single server filesystem.
- Modernize React architecture
- Connect all pages to the new API
- Improve loading/error states
- Modernize routing
- Improve post editing
- Authentication integration
- Better API abstraction
The long-term goal is to turn Message Node into a maintainable full-stack application with:
- Strong TypeScript typing
- Runtime validation
- Clean separation of concerns
- Centralized error handling
- Secure authentication and authorization
- Scalable database access
- Reliable file storage
- Testable business logic
- Production-ready API design
The project is intentionally being developed incrementally, with the backend architecture being improved rather than simply reproducing the original application's implementation.
This project is currently a personal learning/development project.