Unlocking Backend Development: A Beginner's Guide to Building Strong Web Applications

Nikhil SinghNikhil Singh
6 min read

When I began my journey into web development, I often heard from friends and classmates that backend development was complex and challenging. This is a common sentiment among beginners. However, I believe that backend development is easier than frontend development.

Backend development focuses on logic building, without the need for design tools like Figma, which are used in frontend development. As a beginner, I strongly recommend starting with backend development. Although I'm not an expert or advanced coder, I have learned the core concepts of backend development and successfully built the backend for a video-sharing platform and a chess game using JavaScript.

Tech Stack I used

  • NodeJS

  • ExpressJs

  • Web Sockets

  • PostgreSQL (SQL-based Database)

  • Prsima (ORM)

So, to write the backend and the brain of your product, you have to set up a file first and make some utils for it

Async Handler in which will function to handle your user logic in a great way, just a function with req, res, next. It makes it a lot easier to fix any error.

const asyncHandler = (fn) => async (req, res, next) => {
    try {
        await fn(req, res, next)
    } catch (error) {
        const statusCode = typeof error.statusCode === "number" ? error.statusCode : 500 // for the ORM like Primsa
        res.status(statusCode).json({
            success: false,
            message: error.message,
        })
    }
}
export {
    asyncHandler
}

And talking about error, you need error handling to give a status code and an error message, I call it ApiError, in this, a simple class extends Error and inside it a constructor and using this it makes so much easier to give a custom error message and a status code for it and helps to debug and extact file or line of code where is Error.

class apiError extends Error {
    constructor(
        statusCode,
        message = "something went wrong",
        errors = [],
        stack = ""
    ){
        super(message)
        this.statusCode = statusCode,
        this.message = message,
        this.data = null,
        this.success = false,
        this.errors = errors

        if (stack) {
            this.stack = stack
        } else {
            Error.captureStackTrace(this ,this.constructor)
        }

    }
}

export {apiError}

Now, when to send a response to the database or the user console, we have to send a success status code (200), and it can be sent by res.json, but the thing is, we are here to talk about Clean code and everything under your control and manageable. So we have one more file to send a response as you like with data, and custom message, and a status code called ApiResponse. In this file, a basic constructor and this. format it has Data that accepts JSON files. A simple block of code

class apiResponse{
    constructor(
        statusCode,
        data,
        message = "success"
    ){
        this.statusCode = statusCode;
        this.data = data;
        this.message = message;
        this.success = statusCode<400;
    }
}

export { apiResponse }

And now almost all things are stepping up to write your controller, but there is a thing called JSON WEB TOKEN, it's an npm package and a lot more. It gives access to create Access Token and Refresh Token. These are key to store in the Database and a cookie to keep the user logged in for a temporary time until the tokens are generated again. It’s simple to generate them and implement them in controllers when needed, just need jwt. sign (and jwt from JSON Web Token)

import jwt from "jsonwebtoken";
const generateAccessToken = async (user) => {

    return jwt.sign({
        id: user.id,
        email: user.email,
        username: user.username,
    },
        process.env.ACCESS_TOKEN_SECRET,
        {
            expiresIn: process.env.ACCESS_TOKEN_EXPIRY
        }
    )
}
const generateRefreshToken = async (user) => {
    return jwt.sign({
        id: user.id,
        email: user.email,
        username: user.username,
    },
        process.env.REFRESH_TOKEN_SECRET,
        {
            expiresIn: process.env.REFRESH_TOKEN_EXPIRY
        }
    )
}
export {
    generateAccessToken,
    generateRefreshToken
}

To use it and store it in a cookie, just some config there, it can't be done as simply, but this is more secure. and effect

const accessToken =  await generateAccessToken(logUser);
const refreshToken = await generateRefreshToken(logUser);

    const accessTokenOption = {
        httpOnly: true,
        secure: process.env.NODE_ENV === "production",
        sameSite: "strict",
        maxAge: 15*60*1000,
    }
    const refreshTokenOption = {
        httpOnly: true,
        secure: process.env.NODE_ENV === "production",
        sameSite: "strict",
        maxAge: 7*24*60*60*1000,
    }

Let me explain, to send a cookie, you need some config there, as httpOnly and secure are basic, it's just to set it, but if it's not like production, I used this process, and it's only a backend, so sameSite is "strict" if you have frontend just replace the url there and cookie will be there. See told you just need it’s easy

Passwords are a strong and important thing, so you can't just store passwords as passwords given by the user. And if you do, and anyone has database access, they can get the password and use the account to solve this. There is a package called bcrpyt, it's so simple, just read the docs, you will get it, and I only needed to think there “.hash” and “.compare“. Hash for just to encrypt the password and salt rounds in it, and compare to decrypt and match from the database.

These are the utilities you will need when you start writing backend, just export them and use them where you need, mainly to use in the controllers.

To check whether your block of code is working or not, you don't frontend for most things, just use Postman to make routes for a user, for example:

import { Router } from "express";
import { loginUser, registerUser } from "../controllers/user.controllers.js";

const router = Router();

router.route("/register").post(registerUser)
router.route("/login").post(loginUser)


export default router

And then connect it to app.js or the server.js wherever you imported routes, just use it like this

import userRoutes from "./routes/user.routes.js"
app.use("/api/v1/users", userRoutes)

Whenever the URL is passed as “/api/v1/users”, the userRoutes will catch up, and after that, it can be “/register”, “/login”, or “logout”; it will all work.

As I am a beginner, I don't have cool tricks and other things that make backend easier, but you have a strong understanding of JavaScript, and a language that can make backend you can write backend because it's just logic building, not like a DOM manipulation on the frontend. It's a pure logic building. And make a good brain from it, in this, I just have to share the starting point because it's hard to find the starting point as a beginner. I can feel that.

In conclusion, embarking on the journey of mastering backend development can be both exciting and rewarding. By focusing on core concepts and utilising the right tools and techniques, you can build robust and efficient web applications. Remember, the key is to start with a solid foundation, continuously learn, and adapt to new technologies. As you progress, you'll find that backend development not only enhances your problem-solving skills but also opens up a world of opportunities in the tech industry. Keep coding, stay curious, and enjoy the process of creating impactful digital solutions.

Best regards, Nikhil

1
Subscribe to my newsletter

Read articles from Nikhil Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nikhil Singh
Nikhil Singh

Coding to disrupt, not impress. Backend & AI enthusiast | BCA'27 | Learning loud, building louder.