Building Car-Immo: The Real Challenges I’m Facing and How I’m Solving Them
Suggested DEV.to tags:
webdev,node,nextjs,programming
Introduction
Building an application by following a tutorial and developing a product for real users are two very different experiences.
With Car-Immo, my goal is to create a platform that brings two markets together in one place: real estate and vehicles. Users should be able to search for a house, an apartment, a plot of land, or a vehicle. They should also be able to publish a listing and connect with interested buyers or renters.
On paper, the idea sounds simple. In practice, it has made me think deeply about architecture, user roles, database design, deployment, security, and user experience.
In this article, I share the main lessons I have learned while developing Car-Immo. This is not a tutorial presenting a perfect application. It is an honest look at the process of turning an idea into a full-stack product.
1. Start With the Need, Not the Technology
At the beginning of a project, it is tempting to immediately choose a framework, a database, and a long list of libraries. However, the first question should be: What problem does the application need to solve?
Among other things, Car-Immo needs to let users:
- browse real estate and vehicle listings;
- search and filter listings;
- create an account and sign in;
- publish and manage their own listings;
- connect advertisers with potential customers;
- administer and moderate the platform.
This list helped me separate essential features from ideas that could wait for a future version.
Lesson one: A useful product is not measured by the number of technologies it uses. A first version should focus on solving a small number of important problems well.
2. Choose an Architecture You Can Maintain
For Car-Immo, I separated the frontend from the backend.
The frontend uses Next.js and TypeScript. It manages the pages, navigation, forms, and user interface. The backend is built with Node.js, Express, and TypeScript. It exposes a REST API responsible for authentication, users, listings, visits, statistics, and administrative operations.
The data is stored in PostgreSQL, with Prisma ORM used to define the schema and communicate with the database. The backend is also prepared to use Redis when caching or distributed rate limiting becomes necessary.
A simplified view of the architecture looks like this:
Browser
↓
Next.js frontend
↓ HTTPS requests
Express API
↓
Prisma ORM
↓
PostgreSQL
This separation provides flexibility, but it also introduces several questions: Where should each part be deployed? How should CORS be handled? How can communication be secured? How can types be shared without creating too much coupling?
Lesson two: Every architectural decision provides benefits, but it also creates new responsibilities. Choose a solution only after understanding its operational consequences.
3. Design the Data Around the Business Domain
A vehicle listing and a real estate listing share some information: a title, description, price, images, location, owner, and status.
However, they also have different attributes. A vehicle can have a make, model, year, and mileage. A property can have a number of bedrooms, an area, or a transaction type.
This creates a data-modeling decision:
- one large table containing many optional fields;
- two completely separate models;
- one shared model supplemented with category-specific information.
The right choice depends on the expected searches, filters, and future changes. This is not merely a database concern: the data model also shapes the API routes, forms, validation rules, and frontend display.
I also learned that migrations must be treated as part of the product. Changing a schema locally is easy. Doing it in production without losing data requires a clear strategy, versioned migrations, and reliable backups.
Lesson three: A database schema should represent the business domain, not merely the current shape of the user interface.
4. Authentication Is Not Enough: Authorization Matters Too
Knowing that a user is signed in does not tell us what that user is allowed to do.
A platform such as Car-Immo needs several rules:
- users can edit their own listings, but not someone else's;
- administrators can moderate listings and manage users;
- some features may depend on a subscription or account status;
- sensitive routes must be protected on the server, even if their buttons are hidden in the interface.
An interface that hides a button is not a security mechanism. Every important permission must be verified by the API.
Lesson four: Authentication answers, “Who are you?” Authorization answers, “Are you allowed to perform this action?” A real application needs both.
5. An Application That Works Locally Is Not Yet Production-Ready
In my local environment, the frontend and backend could communicate over HTTP. After deploying the frontend to Vercel, the browser began blocking requests to the API.
The frontend was loaded through an HTTPS address, while the backend was still accessible through HTTP. The browser therefore reported a Mixed Content error.
This problem taught me that deployment is not simply the final command in a project. You also need to prepare:
- environment variables;
- HTTPS for all public communication;
- the allowed CORS origins;
- database migrations;
- logs and error messages;
- a health endpoint;
- a backup and rollback procedure.
A variable such as the API URL must also be configured for the correct environment. A value used locally should not be copied automatically into production.
Lesson five: Production should be considered during development, not only after the application is finished.
6. A Good Health Check Makes Troubleshooting Much Easier
I added an /api/health route to check the overall state of the backend. It can indicate whether the server is responding, whether PostgreSQL is reachable, and whether optional services such as Redis are enabled.
Example response:
{
"status": "healthy",
"database": "connected",
"redis": "disabled",
"environment": "production",
"version": "1.0.0"
}
This route does not replace a proper monitoring solution. However, it quickly answers an essential question: Is the service running, and can it reach its main dependencies?
It is also important to avoid exposing secrets, connection details, or complete error traces through this endpoint.
Lesson six: Observability is not reserved for large companies. Even a small project needs useful logs and a simple way to check its status.
7. Docker Provides Consistency, but It Does Not Remove Deployment Decisions
I used a multi-stage Docker image to build the API. One stage installs the dependencies and compiles the TypeScript code; another retains only the files required at runtime.
This approach makes it possible to:
- create a more reproducible environment;
- reduce the final image size;
- separate development dependencies from production dependencies;
- run the same application on different infrastructures.
However, Docker does not automatically configure HTTPS, PostgreSQL backups, firewall rules, or secrets. It packages the application; it does not replace the entire infrastructure.
I also had to distinguish between three Prisma operations that are often confused: generating the client, applying migrations, and inserting initial data. These operations can be automated, but they should not be run blindly every time the application starts.
Lesson seven: A container makes deployment more consistent, but a well-built Docker image alone cannot guarantee a reliable production environment.
8. Design Should Help the User Make a Decision
For a marketplace, an attractive listing card is not enough. Users must quickly understand:
- what is being offered;
- where the property or vehicle is located;
- how much it costs;
- what its important features are;
- what action to take next.
I therefore aimed for a modern, premium visual direction while maintaining a clear hierarchy. Filters, images, loading states, and the mobile experience matter just as much as colors and shadows.
In a context where some users may have limited connectivity, image size and performance are not minor details. They are part of the user experience.
Lesson eight: Design is not merely decorative. It should reduce the effort required to understand, compare, and take action.
9. Errors Are an Excellent Source of Learning
During this project, I encountered errors involving Prisma, PostgreSQL, Docker, CORS, HTTPS, environment variables, and deployment. They slowed down the work at the time, but each one can become useful documentation.
I now try to record the following for every problem:
- the exact error message;
- the context in which it appears;
- the hypotheses I tested;
- the actual cause;
- the solution I chose;
- how to prevent it from happening again.
This method turns debugging into reusable knowledge and also makes it easier to write technical articles.
Conclusion
Car-Immo is still evolving, but this project has already taught me that a full-stack product involves much more than connecting a user interface to a database.
You need to understand the problem, model the data, protect sensitive actions, prepare for deployment, monitor the state of your services, and continuously improve the user experience.
My main conclusion is simple: Real-world projects teach what tutorials rarely show—the interactions between every part of a system.
In the next article in this series, I will explain how I diagnosed a Mixed Content error between a frontend deployed on Vercel and an Express API exposed over HTTP, and why changing the CORS configuration could not solve the problem.
Top comments (0)