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.