Backend Development Demystified: From Structure to Real-Life APIs

So yeah, let’s discuss what backend is, why it matters, what a typical backend structure looks like, what OAuth is, how your database choice affects things, and why having a solid data model and understanding of your app’s purpose is key for writing production-level code. We’ll also talk about file structure and more! In this article, we’re going deep into backend development!!
So let’s begin! In a broader spectrum, backend development involves understanding the differences between backend and frontend, choosing appropriate programming languages (Java, Python, JS, Go, Rust, C#), working with various types of databases (SQL, NoSQL, NewSQL), and learning about different API types (REST, GraphQL, gRPC, SOAP) and authentication methods (JWT, OAuth2, API keys). Additionally, it includes knowledge of server hosting services, containerization, server setup, and DevOps practices such as CI/CD pipelines, Infrastructure as Code (IaC), and monitoring tools.
However, in this particular article, we’re not going to focus on server hosting services, containerization, server setup, and DevOps practices, since they aren’t needed at this stage and can be learned later in the process of building!
Here’s the flow we’ll follow:
Introduction: What is Backend Development?
Core Components of the Backend
Common Tools & Technologies
Backend in Action (Example Workflow)
Key Concepts to Understand
Scaling and Performance
Why Backend Matters
What is Backend?
Sometimes also called the server-side, the backend manages the overall functionality of a web app. For instance, when a user visits your website and likes an image—causing the like count to increase—that action involves sending a request to the backend via HTTP. The backend then processes the request and returns a response.
When the backend processes a request, it typically interacts with:
Database servers to retrieve or modify data
Microservices that perform specific tasks requested by the user
Third-party APIs to gather additional information or perform external functions
Let’s take a classic example. You’re using a shopping app, and you click a button that says “Buy this item.” That request goes to the backend, which then:
→ Receives the request
→ Updates the database
→ Sends back a response
A Practical Life Example: Bakery = Frontend + Backend
Imagine you walk into a bakery. What do you see?
The counter, the menu board, and the display of cakes — these are all parts of the frontend.
It’s everything the customer (you) can see and interact with. It’s designed to be simple, attractive, and user-friendly — just like a website or app UI.
But behind the scenes…
There’s a kitchen, a chef, and ingredients — these make up the backend.
This is where the real processing happens to fulfill your request.
Now, Let’s Say You Order a Chocolate Cake
You tell the person at the counter (the frontend):
“One chocolate cake, please!”
The counterperson sends that order to the chef in the kitchen (the backend).
The chef then checks:
Is the cake already on the shelf? → That’s like checking the database.
If not, do we have the ingredients to make one? → That’s like checking the available APIs or services.
If all is good, the cake is prepared or picked and sent back to the counter.
Before handing it over, the counterperson asks:
“Can I have your order number?”
→ That’s authentication — making sure the cake goes to the right person.
So basically, think of the backend as the brain of your app—it handles what happens behind the scenes when users interact with the interface.
This pretty much clarifies what the backend is! Now, we can move to the core components of the backend.
Core Components of the Backend
The core components of backend development are:
Server (e.g., Express.js, Django, Spring Boot)
Database (SQL vs NoSQL)
APIs (REST, GraphQL)
Authentication & Authorization
Hosting
Servers are the heart of the backend. They manage incoming client requests, process them, and return appropriate responses. They also handle tasks like executing server-side scripts, querying the database, or interacting with third-party services.
Databases store all your app’s data in a structured format. They’re classified into two types: relational and non-relational. We use a query language to access the data, most commonly SQL for relational databases, and various tools for NoSQL databases.
APIs (Application Programming Interfaces) allow frontend and backend to communicate. They act like waiters in a restaurant: when you place an order, the waiter (API) conveys it to the chef (backend) and brings your dish (data).
REST APIs typically deliver predefined data, while real-time APIs (like GraphQL or websockets) can be more dynamic.
Authentication and Authorization are crucial for security. You don’t want unauthorized access or data modification. These mechanisms ensure users are who they claim to be and are allowed to do what they’re trying to do. This includes using protocols like OAuth2, JWT, etc.
A typical backend file structure may follow certain standard patterns. These are often part of a tech stack — a combination of technologies that work well together and are widely supported by the community.
For example, I personally code using:
JavaScript (Node.js)
Express.js (framework)
PostgreSQL or Mongodb (database)
Docker (for DevOps)
There are many other popular stacks and frameworks as well, such as Django, Flask, or Laravel.
so say you are building an application where users can come and upload say videos! what all comes to your mind first!? a user will sign in with all their details, they can upload there videos, there will be a likes counter, a comment section, a subscribe button yeah this much only for now. so to have a good understanding of it will have to create a data model obviously!! so here’s how data model will look like: https://app.eraser.io/workspace/lArF9yzUTTN2tZ8OuiFS?origin=share
remember the better data model you make the better your backend development will be!
for this particular lets make the user and movies thing first here’s how they look:
And now to do a basic setup will obviously need to set up everything in our VS Code and link the DB to the express.js! We are using the Mongodb database for this!
This is how your file structure should look, and we will discuss what each of this do!!
let’s connect the DB to our system, so first things first, make a constant.js and export the db name from it, then move to your .env file and here’s what it should have at least:
PORT = 8000
MONGODB_URI =
CORS_ORIGIN = * #allows requests from anywhere — not a great practice for production.
ACCESS_TOKEN_SECRET = 123 #FOR NOW but generally it is written a complex string genertaed by some algo!
ACCESS_TOKEN_EXPIRY = 1d #not stored in DB
REFRESH_TOKEN_SECRET = 123
REFRESH_TOKEN_EXPIRY = 10d #is generally more than access token!
CLOUDINARY_CLOUD_NAME =
CLOUDINARY_API_KEY =
CLOUDINARY_API_SECRETKEY =
will look at the cloudinary setup later it’s just used to store videos, images and everything, and in the database we just pass the link of it! Since we want to keep the database organised and less cluttered, and not make it very heavy!
Then easy to move to the index.js file of the db directory and write code something like this:
const connectDB = async () => {
try {
const connectionInstance = await mongoose.connect(`${process.env.MONGODB_URI}/${DB_NAME}`)
console.log(`\n MongoDB connected!! ${connectionInstance.connection.host}`);
} catch (error) {
console.log("MongoDB connection error", error);
process.exit(1) //there are diffrent exit codes as well!
}
}
This just connects to the Mongodb we have created from our the uri provided by us and the DB name, which is there and the try catch is obviously for a double check if the DB is properly connected or not and if not what error to throw! Now we can move to our index.js file in the main directory, and for connecting db write this particular code:
connectDB()
.then(() => {
app.listen(process.env.PORT || 8000, () => {
console.log(`server is running on port: ${process.env.PORT}`);
})
})//to define what to do once gets connected to DB
.catch((err) => {
console.log("mongo db connecction failed!", err);
})
//request.params and request.body are the two express requests which will be studied the most!
Now once you run this in your terminal by say “npm run dev” command (you should check in your package.json in the script section to find the exact command!! And your terminal should show something like this:
MongoDB connected!!
server is running on port: 8000
It confirms that yes, your DB is connected!
Now we can move further to connecting the cloudinary, so make a file in utils named cloudinary.js and there write a function to upload photos and images or non-text data there, and then use it in models to write the code when interacting with DB! This is how it should look like:
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRETKEY,
});
const uploadOnCloudinary = async (localFilePath) => {
try{
if (!localFilePath) return null
//upload file on cloudinary
const response = await cloudinary.uploader.upload(localFilePath, {
resource_type: "auto"
})
//file has been uploaded successfully
//console.log("file uploaded on cloudinary", response.url);
//now once file is uploaded i will like to unlink it!
fs.unlinkSync(localFilePath) //not using await and all since i want it to get del instantly!
return response;
} catch (error) {
fs.unlinkSync(localFilePath)//local saved temp file as upload operation got failed
}
}
In this code, i am not writing the import and export statements since these are to focus on the function logics more!
Now once your Cloudinary is ready, we can move further to write other utilities.
utilis is btw a function which is used again and again in different files and mainly they are written to deal with the API! so here are the main must have utils your backend must have along with there code and explaination:
Remember these utilis are generally written in object oriented format!
API Error: it is written to check the demand api is doing and if there is any error in the request or the response being returned it returns what the error is!
class ApiError extends Error {
constructor (
statusCode,
message = "Something went wrong",
errors = [],
stack =""
){
super(message)
this.statusCode = statusCode
this.data = null
this.message = message
this.statusCode = this.errors
if (stack) {
this.stack = stack
} else {
Error.captureStackTrace(this, this.
constructor)
}
}
}
API Response: it is written to check when the API request is being responded then what the structure should be like what status code to return what data and what message to return!
class ApiResponse {
constructor(statusCode, data, message = "Success"){
this.statusCode = statusCode
this.data = data
this.message = message
this.success = statusCode < 400
}
}
asyncHandler: It helps us manage errors in functions that use async/await
. Normally, if something goes wrong inside an async function, Express won’t catch the error on its own. So instead of writing try-catch
blocks everywhere, we wrap our async functions with an async handler. This way, if there's any error, it automatically passes it to Express's error-handling system. It keeps your code clean and avoids repeating the same error-handling code again and again
const asyncHandler = (requestHandler) => {
return (req, res, next) => {
Promise.resolve(requestHandler(req, res, next))
.catch((err) => next(err))
}
}
These are the utils which we will use all over our models! now lets move forward to writing middlewares and why are they needed? we know that we will be uploading images and similar files so for that a multer middleware should be written and middleware is basically if you are going meet me first and then go! so whenever file is uploaded, it will get passed through and then will move further! later on as we will write user then also we will need a middleware!
for now this is how the multer middleware will look like:
const storage = multer.diskStorage({
destination: function (req, file, cb) {
//req comes from user contains all the json data and all and if it contains file as well so file is used!; file contains all the files;cb is callback
cb(null, './public/temp') //this other paraneter is the address where have to store the files!
},
filename: function (req, file, cb) {
//const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9) //this is setting the filename format
cb(null, file.originalname) //here original name can be stored since these files are with us for a very short time only then they go to cloudnary or wherever!
//so originalname can also be stored so that overwrite need not to be taken of that much!
}
})
yes moving to models so models contain basically the files which interact with the database at first! here’s is how user.model.js looks like it just contains the userSchema; and then for say its a user login thing we will write various checks for password, accessToken, refreshToken if you dont know what access and refresh tokens are the way they work is itself an interesting concept! they are basically for security purposes and one gets refreshed in short period of time while other gets refreshed in longer period and both of them confirms by some key and once its confirmed, they are refreshed by this no hacker or other person can access the user data!
here’s how it looks like:
const userSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
index: true //if want to make any field searchable in DB then make its index true!
},
},
{
timestamps: true,
}
)
//userSchema.pre("save", () => {})// don't write this way since in js we need refrence of this! context basically //whatever code want to execurte write heere same likee app.listen and all
userSchema.pre("save", async function (next) {
//next access given when work completed ot flags it and tells to pass next!
if(!this.isModified("password")) return next();
this.password = bcrypt.hash(this.password, 10)
next() //https://youtube.com/clip/Ugkx4iv93ThGPyHd3eRLc73lXrGeqFOYpCia?si=XACpNtKj8JHLW-GI
}) //used async since this type of functions take time to execute!
userSchema.methods.isPasswordCorrect = async function(password){
return await bcrypt.compare(password, this.password)
} //rteurns true or false and await used since cryptography and all used takes time!
//read JWT properly!
userSchema.methods.generateAccessToken = function(){
return jwt.sign(
{
_id: this._id, //is taken from DB
email: this.email,
username: this.username,
fullName: this.fullName
},
process.env.ACCESS_TOKEN_SECRET,
{
expiresIn: process.env.ACCESS_TOKEN_EXPIRY
}
)
}
userSchema.methods.generateRefreshToken = function(){
return jwt.sign(
{
_id: this._id, //is taken from DB
},
process.env.REFRESH_TOKEN_SECRET,
{
expiresIn: process.env.REFRESH_TOKEN_EXPIRY
}
)
}
in the same way we write video model and other models this was one example of writing the code like this only all other models are written!!
now once all our models are done we can move to writing controllers, where all the business logic goes and they use models and utils! here we write everything for example what all the user will do like:
registerUser
loginUser
logoutUser
refreshAccessToken
changeCurrentPassword
getCurrentUser
updateAccountDetails
updateUserAvatar
updateUserCoverImage
getUserChannelProfile
getWatchHistory
and many more!!!
here is a sample how say register user will look like:
const registerUser = asyncHandler(async(req, res) => {
//when registering a user take its name,contact, and other basic details! i.e frontend
//store those details
//ensure user has filled necessary parts also! (validations)
//check if user already exist (by say username or email either)
//check for images and avatar and upload on cloudnary!, avatar check on cloudnary also
//create user object-create entry in db
//remove pswd and refresh token field from response
//check if response came or not
//if created return res
//user details done!
const {fullName, email, username, password} = req.body //idhar se data aara hae for now we are entering raw data in json format in body in postman!
console.log(email, password);
//user validation
// if (fullName === ""){
// throw new ApiError(400, "fullname is required!")
// } to check multiple can do this:
if (
[fullName, email, username, password].some((field) => field?.trim() === "")
){
throw new ApiError(400, "all fields are required!")
} //like this can write mulitple validations in production there is a seperate file for this and from them the are called like methods!
//check if user already exists
const existedUser = await User.findOne({
$or: [{username: username}, {email: email}]
});
if (existedUser) {
throw new ApiError(409, "User with email or username already exist!");
}
console.log(req.files); //like this log all the things and see how files go to cloudinary how they come read them and do shit!
//can see the result is in array with response in objects!
Or how updateAccountDetails should look:
const updateAccountDetails = asyncHandler(async( req, res) => {
const {fileName, email} = req.body
if (!fullName || !email){
throw new ApiError(400, "All fields are required")
}
const user = User.findByIdAndUpdate(
req.user?._id,
{
$set: {
fullName,
email: email
}
},
{new: true}
).select("-password")
return res
.status(200)
.json(new ApiResponse(200, user, "account updated successfully"))
})
I follow this rule that before writing a controller function, write what that function will do, like basically the algorithm of each function, then the code! It makes the understanding really easy!
so basically what all data the models got by interacting with the db now what to do with that is done by controllers!!
you all may write the other controllers on your own and ping me on linkedin or X and showcase how you approached!
now for final endpoints of controllers, we write routes! routes are the place where we write the endpoints which the api will hit and will bring the data from there!
here is an example how user route will look like you can write the other routes the same way!
const router = Router()
router.route("/register").post(
upload.fields([
{
name: "avatar", //frontend mai jjo field bnega uska name bhi same hona chiye
maxCount: 1
},
{
name: "coverImage", //my this name was wrong in frontend coming data and on this multer!!
//keep the name of all the variables same at any cost! across frontend and backend have a naming consistency! ft jyega ni toh!
maxCount: 1
}
]),
registerUser
)
router.route("/update-account").patch(verifyJWT, updateAccountDetails)
now to use this we will have to import it in our app.js use it by writing the endpoint!
import userRouter from './routes/user.routes.js'
app.use("/api/v1/users", userRouter)
now if the endpoint of the url link is “api/v1/users” then write there it will be redirected to user route! and you can do all the things written in user.controller.js!
so yeah this was pretty much backend majorly it is a vast field and a lot of things are there to dive in but yes this was a pretty much basic to give you a feel what backend is in real sense and how to write a production level scalable code! i tried to touch on all the key concepts if got wrong somewhere or missed something comment section is open for you guys!
a very common mistake people make while writing backend code is in naming remember things are case sensitive and a small spelling mistake can take your hours!!
So yeah, that was a deep dive into backend development — from what it is, to how it works, to actually setting stuff up and getting your hands dirty. Backend isn’t just about writing code; it’s about building systems that can scale, perform, and most importantly, make sense. The better your structure, your data modeling, your utils, your APIs — the smoother your entire application runs.
At the end of the day, it’s not about knowing everything right away. It’s about getting started, breaking things, learning from it, and improving. Every big tech product you admire started out messy too — the key is to start building.
So go ahead, fire up your terminal, spin up that server, and start piecing your backend together — one endpoint at a time.
More coming soon. Until then, keep coding, keep shipping. 💻🔥
Subscribe to my newsletter
Read articles from Kartik directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Kartik
Kartik
I am a developer from India! here i will be posting blogs as i will be learning in my development journey! i make pretty much mistakes while writing my programs and i learn majorly through my projects only so will be posting that as well!( yes i will be blogging pretty much every shit i do as a developer:)