What is JavaScript? From Browser Scripts to Full-Stack Development
JavaScript is the programming language that runs the web. Every interactive element you see on a website – dropdown menus, form validations, dynamic content updates, animations – is powered by JavaScript. It's the only programming language that runs natively in web browsers, making it one of the most widely used languages in the world.
In my previous article, What is PHP?, I explained how PHP dominated my web development career from 2005 to 2018. Around 2017 and 2018, I made the transition away from PHP and into full-stack JavaScript development. This shift wasn't because PHP was bad, but because the JavaScript ecosystem had matured to the point where I could build both frontend and backend with a single language.
This article examines what JavaScript is, how I used it throughout my career, the rise of NodeJS that changed everything, and how modern JavaScript development has evolved into a complete ecosystem for building any type of application.
What is JavaScript?
JavaScript was created by Brendan Eich in 1995 while he was working at Netscape Communications. The language was originally developed in just 10 days and was initially called Mocha, then LiveScript, before finally being renamed JavaScript.
Despite the name similarity, JavaScript has nothing to do with Java. The name was purely a marketing decision to capitalize on Java's popularity at the time. JavaScript is a high-level, interpreted programming language that conforms to the ECMAScript specification.
Key Characteristics:
- Dynamically Typed - Variables don't require type declarations
- Prototype-Based - Objects inherit directly from other objects
- First-Class Functions - Functions can be assigned to variables, passed as arguments, and returned from other functions
- Event-Driven - Built around responding to user actions and events
- Single-Threaded - Executes one operation at a time (but with asynchronous capabilities)
JavaScript runs in web browsers through JavaScript engines. Chrome uses V8, Firefox uses SpiderMonkey, and Safari uses JavaScriptCore. These engines compile JavaScript to machine code for fast execution.
My Early Years: DOM Manipulation and AJAX
For literally years, I mainly used JavaScript for DOM manipulation and AJAX functionality. This was the bread and butter of web development in the late 2000s and early 2010s.
DOM Manipulation
The Document Object Model (DOM) represents the HTML structure of a page as a tree of objects. JavaScript allows you to dynamically modify this structure, changing content, styling, and behavior without reloading the page.
Here's a common pattern I used constantly:
const button = document.getElementById('submit-button');
button.addEventListener('click', function(event)
{
event.preventDefault();
const username = document.getElementById('username').value;
const email = document.getElementById('email').value;
if (!username || !email)
{
alert('Please fill in all fields');
return;
}
button.disabled = true;
button.textContent = 'Submitting...';
submitForm(username, email);
});This kind of code was everywhere in my early JavaScript work. Every form, every interactive element, every dynamic behavior required manual DOM manipulation.
AJAX: Asynchronous JavaScript and XML
AJAX revolutionized web development by allowing pages to request and receive data from servers without full page reloads. Despite the name including "XML," most modern AJAX actually uses JSON.
Here's how I handled AJAX requests around 2010:
$.ajax({
url: '/api/users',
method: 'POST',
data:
{
username: username,
email: email
},
success: function(response)
{
$('#message').text('User created successfully!');
button.disabled = false;
button.textContent = 'Submit';
},
error: function(xhr, status, error)
{
alert('Error: ' + error);
button.disabled = false;
button.textContent = 'Submit';
}
});Later, as browsers improved, I transitioned to the native Fetch API.
fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: username,
email: email
})
})
.then(response => response.json())
.then(data => {
document.getElementById('message').textContent = 'User created successfully!';
button.disabled = false;
button.textContent = 'Submit';
})
.catch(error => {
alert('Error: ' + error.message);
button.disabled = false;
button.textContent = 'Submit';
});This pattern – manipulating the DOM and making AJAX requests – defined my JavaScript usage for years. Every website I built followed this approach.
The Rise of NodeJS
Everything changed in 2009 when Ryan Dahl created NodeJS. For the first time, JavaScript could run outside the browser on the server side.
What Made NodeJS Revolutionary
NodeJS combined Google's V8 JavaScript engine with an event-driven, non-blocking I/O model. This made it incredibly efficient for handling concurrent connections, which was perfect for building real-time applications.
Key Milestones in NodeJS
2009 - Ryan Dahl presented NodeJS at the European JSConf, demonstrating server-side JavaScript with impressive performance.
2010 - NPM (Node Package Manager) was introduced by Isaac Z. Schlueter. This was a game-changer that allowed developers to easily share and install packages. Today, NPM has over 2 million packages, making it the largest software registry in the world.
2011 - Major companies like LinkedIn and Uber began adopting NodeJS for their backends, citing massive performance improvements and developer productivity gains.
2014-2015 - The io.js fork occurred due to concerns about slow updates and governance. This eventually led to better community involvement and the formation of the NodeJS Foundation.
2015 - The NodeJS Foundation was created, with support from Microsoft, IBM, PayPal, and Intel. The io.js fork merged back into NodeJS, strengthening the project.
2017 - The year of mainstream adoption. NodeJS instances reached 8.8 million online, with 3 billion NPM package downloads per week. NodeJS was ranked the 4th most important open-source project.
2018 - NodeJS 10.x was released with ECMAScript module support, marking another major step forward.
2019 - The NodeJS Foundation and JS Foundation merged to form the OpenJS Foundation, further consolidating the JavaScript ecosystem.
2023 - NodeJS 20 was released with major features including native test runner, permission model improvements, and better stability.
2024 - NodeJS 22.6.0 introduced native TypeScript support, allowing TypeScript files to be executed without a separate compilation step.
Discovering NodeJS
I learned about NodeJS around 2017 and 2018, which was perfect timing. By then, the ecosystem had matured significantly. The early instability and governance issues had been resolved, NPM had become indispensable, and major frameworks had emerged.
Node Applications I Explored
When learning NodeJS, I explored several popular applications built on the platform:
Ghost - An open-source blogging platform that I eventually migrated CoderOasis to in 2019. Ghost was beautifully designed and demonstrated what was possible with NodeJS for content management. It was fast, modern, and developer-friendly.
NodeBB - A NodeJS-based forum software that showed how real-time features could be integrated into traditional discussion platforms. The WebSocket integration for live updates was impressive compared to the PHP forums I had worked with.
Other Popular NodeJS Apps - I also looked at various API servers, real-time chat applications, and productivity tools built with NodeJS to understand different architectural patterns.
What struck me about these applications was their performance and the developer experience. The same language on frontend and backend, combined with NPM's massive ecosystem, made development significantly faster than the PHP/JavaScript split I was used to.
Discord Bot Development
In 2018 and 2019, I got into Discord bot development with DiscordJS alongside a few good friends. This was an excellent way to learn NodeJS in a practical, fun environment.
I made small to medium-sized bots ranging from moderation tools to custom bots for small communities and different utility features as people requested them.
Simple Moderation Bot
Here's a very simplified version of a moderation bot.
const Discord = require('discord.js');
const client = new Discord.Client();
const prefix = '!';
const moderatorRole = 'Moderator';
client.on('ready', () => {
console.log(`Logged in as ${client.user.tag}!`);
client.user.setActivity('Moderating the server', { type: 'WATCHING' });
});
client.on('message', async message => {
if (message.author.bot) return;
if (!message.content.startsWith(prefix)) return;
const args = message.content.slice(prefix.length).trim().split(/ +/);
const command = args.shift().toLowerCase();
const isModerator = message.member.roles.cache.some(role =>
role.name === moderatorRole
);
if (command === 'kick') {
if (!isModerator) {
return message.reply('You do not have permission to use this command.');
}
const member = message.mentions.members.first();
if (!member) {
return message.reply('Please mention a user to kick.');
}
const reason = args.slice(1).join(' ') || 'No reason provided';
try {
await member.kick(reason);
message.channel.send(`${member.user.tag} has been kicked. Reason: ${reason}`);
} catch (error) {
message.reply('I was unable to kick the member.');
}
}
if (command === 'ban') {
if (!isModerator) {
return message.reply('You do not have permission to use this command.');
}
const member = message.mentions.members.first();
if (!member) {
return message.reply('Please mention a user to ban.');
}
const reason = args.slice(1).join(' ') || 'No reason provided';
try {
await member.ban({ reason: reason });
message.channel.send(`${member.user.tag} has been banned. Reason: ${reason}`);
} catch (error) {
message.reply('I was unable to ban the member.');
}
}
if (command === 'purge') {
if (!isModerator) {
return message.reply('You do not have permission to use this command.');
}
const amount = parseInt(args[0]);
if (isNaN(amount) || amount < 1 || amount > 100) {
return message.reply('Please provide a number between 1 and 100.');
}
try {
await message.channel.bulkDelete(amount + 1);
const msg = await message.channel.send(`Deleted ${amount} messages.`);
setTimeout(() => msg.delete(), 5000);
} catch (error) {
message.reply('I was unable to delete messages.');
}
}
});
client.login('YOUR_BOT_TOKEN');Building Discord bots taught me important concepts about event-driven programming, asynchronous operations, API interaction, and error handling. These skills transferred directly to web development.
Serious Web Development
From 2021 to 2023, I dove deep into serious web development with NodeJS and the modern JavaScript ecosystem. This is when I learned about Deno, React, TypeScript, and TailwindCSS.
Deno: A Modern Runtime
Deno is a secure runtime for JavaScript and TypeScript created by Ryan Dahl (the same person who created NodeJS). He built Deno to address what he considered design mistakes in NodeJS.
Deno offers several improvements:
- Native TypeScript support without configuration
- Secure by default (requires explicit permissions for file, network, and environment access)
- Built-in tooling (formatter, linter, test runner)
- No
package.jsonornode_modules– imports are URL-based - Top-level await support
Here's a simple Deno HTTP server.
import { serve } from "https://deno.land/[email protected]/http/server.ts";
const handler = async (request: Request): Promise<Response> => {
const url = new URL(request.url);
if (url.pathname === "/api/hello") {
return new Response(
JSON.stringify({ message: "Hello from Deno!" }),
{
headers: { "Content-Type": "application/json" }
}
);
}
return new Response("Not Found", { status: 404 });
};
console.log("Server running on http://localhost:8000");
await serve(handler, { port: 8000 });While Deno is interesting, NodeJS still dominates the ecosystem. I explored Deno to understand modern JavaScript runtime design but continued using NodeJS for production projects.
React: Component-Based UI
React revolutionized how we build user interfaces by introducing a component-based architecture. Instead of manipulating the DOM directly like I did for years, React uses a virtual DOM and declarative syntax.
Here's a practical React component I might build.
import React, { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/users')
.then(response => {
if (!response.ok) {
throw new Error('Failed to fetch users');
}
return response.json();
})
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, []); // Empty dependency array means run once on mount
if (loading) {
return <div className="text-center p-4">Loading...</div>;
}
if (error) {
return <div className="text-red-600 p-4">Error: {error}</div>;
}
return (
<div className="max-w-4xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Users</h1>
<div className="grid gap-4">
{users.map(user => (
<div key={user.id} className="border rounded-lg p-4 shadow">
<h2 className="text-xl font-semibold">{user.name}</h2>
<p className="text-gray-600">{user.email}</p>
<p className="text-sm text-gray-500">
Joined: {new Date(user.createdAt).toLocaleDateString()}
</p>
</div>
))}
</div>
</div>
);
}
export default UserList;The component manages its own state, handles side effects through hooks, and renders declaratively. This is far cleaner than the jQuery-based DOM manipulation I used to write.
TypeScript: Adding Types to JavaScript
TypeScript adds static typing to JavaScript, catching errors at compile time instead of runtime. This dramatically improves code quality and developer experience.
Here's the same component that is written in TypeScript.
import React, { useState, useEffect } from 'react';
interface User {
id: number;
name: string;
email: string;
createdAt: string;
}
function UserList(): JSX.Element {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchUsers = async (): Promise<void> => {
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error('Failed to fetch users');
}
const data: User[] = await response.json();
setUsers(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
if (loading) {
return <div className="text-center p-4">Loading...</div>;
}
if (error) {
return <div className="text-red-600 p-4">Error: {error}</div>;
}
return (
<div className="max-w-4xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Users</h1>
<div className="grid gap-4">
{users.map((user: User) => (
<div key={user.id} className="border rounded-lg p-4 shadow">
<h2 className="text-xl font-semibold">{user.name}</h2>
<p className="text-gray-600">{user.email}</p>
<p className="text-sm text-gray-500">
Joined: {new Date(user.createdAt).toLocaleDateString()}
</p>
</div>
))}
</div>
</div>
);
}
export default UserList;TypeScript provides autocomplete, type checking, and better refactoring support. The initial setup overhead is worth it for any project beyond a simple script.
TailwindCSS: Utility-First Styling
TailwindCSS changed how I write CSS by providing utility classes instead of writing custom CSS. The className attributes in the examples above use Tailwind classes.
Instead of this traditional approach that follows.
.user-card {
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
padding: 1rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
}
.user-name {
font-size: 1.25rem;
font-weight: 600;
}You use utility classes directly.
<div className="border border-gray-200 rounded-lg p-4 shadow">
<h2 className="text-xl font-semibold">{user.name}</h2>
</div>This approach is faster, more maintainable, and prevents CSS bloat. You don't context-switch between files, and unused classes are automatically purged in production builds.
Building the New CoderOasis
In July 2025, I decided to code a new version of CoderOasis as a community learning platform. This project is currently in beta, being tested by a few good friends before public release.
The stack for this project includes:
- NodeJS - Backend server and API
- TypeScript - Type-safe development
- React - Component-based UI
- TailwindCSS - Utility-first styling
- PostgreSQL - Relational database
- Redis - Caching and session management
This project represents everything I've learned about JavaScript development over the years, combining modern best practices with lessons from building Discord bots, exploring NodeJS applications, and working with various frameworks.
API Route Handler
Here's a simplified version of an API route from the new CoderOasis:
import { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function getCourses(req: Request, res: Response): Promise<void> {
try {
const { category, difficulty, page = 1, limit = 20 } = req.query;
const where: any = {};
if (category) {
where.category = category;
}
if (difficulty) {
where.difficulty = difficulty;
}
const skip = (Number(page) - 1) * Number(limit);
const [courses, total] = await Promise.all([
prisma.course.findMany({
where,
skip,
take: Number(limit),
include: {
author: {
select: {
id: true,
name: true,
avatar: true
}
},
_count: {
select: {
enrollments: true,
lessons: true
}
}
},
orderBy: {
createdAt: 'desc'
}
}),
prisma.course.count({ where })
]);
res.json({
courses,
pagination: {
page: Number(page),
limit: Number(limit),
total,
totalPages: Math.ceil(total / Number(limit))
}
});
} catch (error) {
console.error('Error fetching courses:', error);
res.status(500).json({ error: 'Internal server error' });
}
}This code demonstrates modern NodeJS development: TypeScript for type safety, async/await for clean asynchronous code, Prisma for database queries, and proper error handling.
The JavaScript Ecosystem Today
The JavaScript ecosystem has evolved dramatically since I started using it for DOM manipulation. Today, JavaScript is a complete platform for building any type of application.
What You Can Build with JavaScript
- Web applications (frontend and backend)
- Mobile applications (React Native, Ionic)
- Desktop applications (Electron)
- Server-side APIs (NodeJS, Deno, Bun)
- Command-line tools
- IoT and embedded systems
- Machine learning applications
- Game development
- Browser extensions
The Modern JavaScript Developer Experience:
Tools and frameworks that make JavaScript development productive:
- Package Managers - NPM, Yarn, PNPM for dependency management
- Build Tools - Vite, Webpack, Rollup for bundling
- Testing - Jest, Vitest, Playwright for quality assurance
- Linting - ESLint, Prettier for code quality
- Type Checking - TypeScript for compile-time safety
- Frameworks - React, Vue, Svelte, Angular for UI development
- Backend Frameworks - Express, Fastify, NestJS, Hono for server development
The ecosystem can be overwhelming for newcomers, but it provides solutions for virtually any problem.
The Conclusion
JavaScript has evolved from a simple browser scripting language into a complete platform for full-stack development. My journey with JavaScript mirrors this evolution – starting with basic DOM manipulation and AJAX, discovering NodeJS and server-side JavaScript, building Discord bots for fun and learning, diving deep into modern frameworks and tools, and now building complex applications like the new CoderOasis platform.
What JavaScript Gave Me:
- The ability to build full-stack applications with a single language
- Access to the largest package ecosystem in the world (NPM)
- A thriving community with constant innovation
- Tools and frameworks that make development productive and enjoyable
- Skills that transfer across web, mobile, desktop, and server development
The transition from PHP to JavaScript wasn't about one language being better than the other. It was about the JavaScript ecosystem maturing to the point where it offered a better developer experience and unified full-stack development. Being able to use the same language, share code between frontend and backend, and leverage modern tooling made JavaScript the right choice for my current projects.
For anyone starting web development today, JavaScript is an excellent choice. The language has matured, the ecosystem is robust, and the skills you learn apply across multiple domains. Whether you're building websites, mobile apps, desktop applications, or server-side APIs, JavaScript has the tools and frameworks to make it happen.
