fervor [>]CODING & CURIOSITY
FERVOR LEARNING SYSTEMTUTORIALS
← Projects

Projects / 17 MIN READ

Kanban Task Board Backend

Kanban Task Board Backend

From the original Fervor library. Examples may use older package versions.

Below is a comprehensive tutorial on setting up the backend for your Kanban Task Board. In this tutorial, we’ll create a simple Express server, connect it to MongoDB, define a Task model, and create CRUD routes for task management.


Tutorial: Setting Up the Backend for a Kanban Task Board

In this tutorial, we’ll build a backend using Node.js, Express, and MongoDB. This backend will support CRUD (Create, Read, Update, Delete) operations for tasks on a Kanban board. We’ll also explain the core concepts and code along the way.


1. Initialize the Project

Step 1: Create a New Directory

First, create a new directory for your backend project and navigate into it:

mkdir kanban-backend && cd kanban-backend

Step 2: Initialize npm

Initialize your project with a package.json file by running:

npm init -y

This command creates a default package.json file that keeps track of your project’s dependencies and metadata.

Step 3: Install Dependencies

We’ll need several packages to build our backend:

  • express: A minimal and flexible Node.js web application framework.
  • mongoose: An Object Data Modeling (ODM) library for MongoDB and Node.js.
  • cors: A package to enable Cross-Origin Resource Sharing, allowing your frontend to communicate with your backend.
  • body-parser: Middleware to parse incoming request bodies (note: with Express 4.16+, JSON parsing is built in).

Install these by running:

npm install express mongoose cors body-parser

2. Set Up the Express Server

Next, create an index.js file which will serve as the main entry point for your server.

Step 1: Import Required Modules

At the top of index.js, import Express, Mongoose, and CORS:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');

Step 2: Initialize the Express Application

Create an instance of Express and apply the middleware:

const app = express();

// Enable CORS to allow requests from other origins (e.g., your React frontend)
app.use(cors());

// Parse incoming JSON requests; this replaces the need for body-parser in most cases.
app.use(express.json());

Step 3: Connect to MongoDB

We’ll connect to a MongoDB database named kanban. If you’re running MongoDB locally, use the connection string below:

mongoose.connect('mongodb://localhost/kanban', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
.then(() => console.log('MongoDB connected'))
.catch(err => console.log(err));

Concepts Explained:

  • mongoose.connect: This function connects your application to a MongoDB database.
  • useNewUrlParser & useUnifiedTopology: These options help avoid deprecation warnings and use the new connection logic.

Step 4: Start the Server

Decide on a port (default is 5000) and start listening for incoming requests:

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Your index.js file should now look like this:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');

const app = express();
app.use(cors());
app.use(express.json());

// Connect to MongoDB
mongoose.connect('mongodb://localhost/kanban', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
.then(() => console.log('MongoDB connected'))
.catch(err => console.log(err));

// Start server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

3. Define the Task Model

We’ll now create a model that represents a task in our Kanban board. Models in Mongoose allow us to define the structure of documents in a MongoDB collection.

Step 1: Create a File Named Task.js

Inside your project directory, create a new file called Task.js.

Step 2: Define the Schema

A schema defines the structure of a document. For our task model, we include:

  • title: A required string.
  • description: An optional string.
  • status: A string that can only be one of 'To Do', 'In Progress', or 'Done'. It defaults to 'To Do'.
  • createdAt: The date when the task was created, defaulting to the current date.
const mongoose = require('mongoose');

const TaskSchema = new mongoose.Schema({
  title: { type: String, required: true },
  description: String,
  status: { type: String, enum: ['To Do', 'In Progress', 'Done'], default: 'To Do' },
  createdAt: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Task', TaskSchema);

Concepts Explained:

  • Schema: The blueprint for your MongoDB documents.
  • enum: Restricts the possible values for a field.
  • default: Specifies a default value if none is provided.

4. Create CRUD Routes

We now create routes to handle creating, reading, updating, and deleting tasks.

Step 1: Import the Task Model

At the top of your index.js file (or in a separate routes file if you prefer), import the Task model:

const Task = require('./Task');

Step 2: Define the Routes

Create a New Task

Use an HTTP POST request to create a task:

app.post('/tasks', async (req, res) => {
  try {
    const task = new Task(req.body); // req.body should contain title, description, etc.
    await task.save();
    res.status(201).json(task);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

Concepts:

  • req.body: Contains the data sent from the client.
  • await task.save(): Saves the task document to MongoDB.
  • Status Code 201: Indicates a resource was successfully created.

Read All Tasks

Retrieve all tasks with an HTTP GET request:

app.get('/tasks', async (req, res) => {
  try {
    const tasks = await Task.find();
    res.json(tasks);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Concepts:

  • Task.find(): Retrieves all documents from the Task collection.
  • Status Code 500: Indicates a server error if something goes wrong.

Update a Task

Update a task by its ID with an HTTP PUT request:

app.put('/tasks/:id', async (req, res) => {
  try {
    const task = await Task.findByIdAndUpdate(req.params.id, req.body, { new: true });
    res.json(task);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

Concepts:

  • req.params.id: Gets the task ID from the URL.
  • findByIdAndUpdate: Finds a document by its ID and updates it.
  • { new: true }: Returns the updated document rather than the old one.

Delete a Task

Remove a task using an HTTP DELETE request:

app.delete('/tasks/:id', async (req, res) => {
  try {
    await Task.findByIdAndDelete(req.params.id);
    res.json({ message: 'Task deleted' });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

Concepts:

  • findByIdAndDelete: Finds a document by its ID and deletes it.
  • Status Code 400: Indicates a bad request if deletion fails.

Your index.js now includes CRUD routes for task management.


5. Testing the API

To ensure everything works as expected, you can test your API endpoints using tools like Postman or cURL.

Testing with Postman:

  1. Create a Task:

    • Set method to POST.
    • URL: http://localhost:5000/tasks.
    • In the body, use JSON format (e.g., {"title": "Learn Express", "description": "Study Express and build APIs"}).
  2. Get All Tasks:

    • Set method to GET.
    • URL: http://localhost:5000/tasks.
  3. Update a Task:

    • Set method to PUT.
    • URL: http://localhost:5000/tasks/{taskId} (replace {taskId} with the actual task ID).
    • In the body, include updated fields (e.g., {"status": "In Progress"}).
  4. Delete a Task:

    • Set method to DELETE.
    • URL: http://localhost:5000/tasks/{taskId}.

Conclusion

You’ve now set up a backend for your Kanban Task Board project. In this tutorial, you learned how to:

  • Initialize a Node.js project.
  • Set up an Express server with middleware.
  • Connect to a MongoDB database using Mongoose.
  • Define a Mongoose model for tasks.
  • Create CRUD routes to manage tasks.
  • Test the API using Postman or cURL.

This backend will serve as the foundation for your full-stack Kanban board, enabling you to create, read, update, and delete tasks, which your React frontend can consume. Feel free to expand on this foundation as you build out the full application!

There are several ways to host your API. Below, I’ll walk you through two popular methods: deploying to Heroku and deploying to a Virtual Private Server (VPS) like DigitalOcean. Each method has its own setup process, so choose the one that best fits your needs.


Sure! Below is the full structure of your Kanban board backend using Node.js, Express, and MongoDB. I’ll include the necessary files, folder structure, and full code.


📂 Project Structure

kanban-backend/
│-- node_modules/
│-- package.json
│-- server.js
│-- config/
│   ├── db.js
│-- models/
│   ├── Task.js
│-- routes/
│   ├── taskRoutes.js
│-- middleware/
│   ├── errorMiddleware.js

🛠 1. Install Dependencies

Run the following command in your project directory:

npm init -y
npm install express mongoose cors dotenv

📄 2. server.js (Main Entry File)

const express = require('express');
const cors = require('cors');
const connectDB = require('./config/db');
const taskRoutes = require('./routes/taskRoutes');
require('dotenv').config();

const app = express();

// Connect to MongoDB
connectDB();

// Middleware
app.use(cors());
app.use(express.json());

// Routes
app.use('/tasks', taskRoutes);

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`🚀 Server running on port ${PORT}`));

📄 3. config/db.js (Database Connection)

const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost/kanban', {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    console.log('✅ MongoDB connected');
  } catch (error) {
    console.error('❌ MongoDB connection error:', error.message);
    process.exit(1);
  }
};

module.exports = connectDB;

📄 4. models/Task.js (Task Model)

const mongoose = require('mongoose');

const TaskSchema = new mongoose.Schema({
  title: { type: String, required: true },
  description: { type: String, default: '' },
  status: { type: String, enum: ['To Do', 'In Progress', 'Done'], default: 'To Do' },
  createdAt: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Task', TaskSchema);

📄 5. routes/taskRoutes.js (Task Routes)

const express = require('express');
const Task = require('../models/Task');

const router = express.Router();

// Create a new task
router.post('/', async (req, res) => {
  try {
    const task = new Task(req.body);
    await task.save();
    res.status(201).json(task);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// Get all tasks
router.get('/', async (req, res) => {
  try {
    const tasks = await Task.find();
    res.json(tasks);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Update a task
router.put('/:id', async (req, res) => {
  try {
    const task = await Task.findByIdAndUpdate(req.params.id, req.body, { new: true });
    res.json(task);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// Delete a task
router.delete('/:id', async (req, res) => {
  try {
    await Task.findByIdAndDelete(req.params.id);
    res.json({ message: 'Task deleted' });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

module.exports = router;

const errorHandler = (err, req, res, next) => {
  const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
  res.status(statusCode);
  res.json({
    message: err.message,
    stack: process.env.NODE_ENV === 'production' ? null : err.stack,
  });
};

module.exports = errorHandler;

Create a .env file in the root of your project:

MONGO_URI=mongodb://localhost/kanban
PORT=5000

🚀 Running the Server

To start the backend server, run:

node server.js

or if you have nodemon installed:

nodemon server.js

🎯 Summary

  • server.js: Main entry point, connects to MongoDB, sets up middleware, and routes.
  • config/db.js: Handles MongoDB connection.
  • models/Task.js: Defines the Task schema for MongoDB.
  • routes/taskRoutes.js: Defines all CRUD routes.
  • middleware/errorMiddleware.js: Error handling middleware (optional).
  • .env: Stores environment variables like MONGO_URI and PORT.

Now you have a fully functional Express + MongoDB backend for a Kanban board! 🎉

1. Deploying to Heroku

Heroku is a Platform-as-a-Service (PaaS) that makes it very easy to deploy Node.js applications.

Step 1: Prepare Your Application

  • Add a Start Script:
    In your package.json, add a start script if you haven’t already:

    "scripts": {
      "start": "node index.js"
    }
    
  • Port Configuration:
    Make sure your app listens on process.env.PORT as shown in your code:

    const PORT = process.env.PORT || 5000;
    app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
    
  • Environment Variables:
    Instead of hardcoding your MongoDB connection string, use an environment variable (e.g., MONGODB_URI). Update your connection code:

    const mongoURI = process.env.MONGODB_URI || 'mongodb://localhost/kanban';
    mongoose.connect(mongoURI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    })
    .then(() => console.log('MongoDB connected'))
    .catch(err => console.log(err));
    

Step 2: Set Up a Git Repository

If you haven’t already, initialize a Git repository:

git init
git add .
git commit -m "Initial commit"

Step 3: Create a Heroku App

  1. Install the Heroku CLI:
    Download and install the Heroku CLI.

  2. Login to Heroku:
    Run:

    heroku login
    
  3. Create a New App:
    In your project directory, run:

    heroku create your-app-name
    

    Replace your-app-name with a unique name.

Step 4: Add a MongoDB Add-on or Use an External MongoDB

  • Option A: Use an add-on:
    Heroku offers add-ons like mLab (now part of MongoDB Atlas) to host your database.

    heroku addons:create mongolab:sandbox
    

    This will set an environment variable (MONGODB_URI or similar) for you automatically.

  • Option B: Use MongoDB Atlas:
    If you already have a MongoDB Atlas cluster, configure your connection string in Heroku:

    heroku config:set MONGODB_URI="your-mongodb-atlas-connection-string"
    

Step 5: Deploy Your Code

Push your code to Heroku:

git push heroku master

Heroku will detect your Node.js app, install dependencies, build your app, and start it using your start script.

Step 6: Monitor Your App

  • Use Heroku logs to troubleshoot:
    heroku logs --tail
    
  • Visit your app using the URL provided by Heroku (e.g., https://your-app-name.herokuapp.com).

2. Deploying to a Virtual Private Server (VPS) like DigitalOcean

If you prefer more control, you can deploy your API on a VPS.

Step 1: Set Up Your Server

  • Choose a VPS Provider:
    Create an account on DigitalOcean, AWS EC2, Linode, etc.
  • Create a New Server Instance:
    Choose an image that supports Node.js (Ubuntu is a common choice).

Step 2: Install Node.js and MongoDB (if needed)

SSH into your server:

ssh your-user@your-server-ip

Then install Node.js (and MongoDB if you’re running it locally) using the package manager. For Ubuntu, you might use:

curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs

For MongoDB, follow the official installation guide.

Step 3: Transfer Your Code

  • Option A: Clone Your Git Repository:
    Make sure your repository is on GitHub or another Git hosting service:

    git clone https://github.com/yourusername/kanban-backend.git
    cd kanban-backend
    
  • Option B: Use SFTP/FTP:
    Upload your code directly to your VPS.

Step 4: Install Dependencies and Start Your App

Install the project dependencies:

npm install

Step 5: Use a Process Manager

To keep your app running and automatically restart it if it crashes, use a process manager like PM2:

npm install -g pm2
pm2 start index.js --name "kanban-api"

Step 6: Configure a Reverse Proxy (Optional)

For production use, you might want to configure a reverse proxy like Nginx to handle HTTPS, serve static files, and forward requests to your Node.js application. This requires additional setup:

  1. Install Nginx:
    sudo apt-get install nginx
    
  2. Configure Nginx as a Reverse Proxy:
    Edit the Nginx configuration file (e.g., /etc/nginx/sites-available/default) to include:
    server {
        listen 80;
        server_name your-domain.com;
    
        location / {
            proxy_pass http://localhost:5000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }
    }
    
  3. Restart Nginx:
    sudo systemctl restart nginx
    

Step 7: Secure Your API

  • SSL/TLS:
    Use Let’s Encrypt to secure your API with HTTPS.
  • Environment Variables:
    Set up environment variables for sensitive data (like your MongoDB connection string).

Summary

  • Heroku:

    • Quick and easy deployment.
    • Automatically manages scaling and environment variables.
    • Great for small to medium projects.
  • VPS (DigitalOcean, etc.):

    • More control over the server environment.
    • Requires more setup (server, process management, reverse proxy).
    • Ideal for larger projects or when you need more custom configuration.

By following these steps, you can host your Node.js API and make it accessible for your frontend and users. Choose the method that best fits your needs and scale as your project grows!


There are several free options to deploy your backend. Here are a few popular ones:

Heroku: Heroku used to offer a generous free tier for Node.js apps, and although they’ve made changes over time, you might still be able to deploy small projects for free. It’s straightforward with Git integration.

Render: Render offers a free tier that supports Node.js applications. It’s similar to Heroku in ease of deployment and provides continuous deployment directly from GitHub.

Railway: Railway provides a free tier with credits that can be used to host small projects, including Node.js backends.

Vercel: Primarily known for frontends, Vercel also supports serverless functions. You can deploy your API as serverless endpoints on the free plan.

Glitch: Glitch allows you to deploy Node.js applications quickly with a collaborative, browser-based environment. It’s a great option for prototypes or smaller projects.

Each of these platforms has its own limitations in terms of uptime, performance, or resource limits on the free tier, but they’re excellent for learning, testing, or small-scale projects


Below are two examples of cool features you can add with code samples: one for real-time collaboration with Socket.io and one for user authentication using JWT. You can integrate these into your Kanban API to enhance its functionality.


Example 1: Real-Time Collaboration with Socket.io

Using Socket.io, you can notify all connected clients when a task is updated (or added/deleted). This way, if one user moves a task, everyone else sees the update immediately.

Step 1: Install Socket.io

npm install socket.io

Step 2: Update Your Server Code (index.js)

Replace your standard Express server with an HTTP server that Socket.io can attach to. Then listen for events (e.g., when a task is updated) and broadcast them to other clients:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const http = require('http');
const socketIo = require('socket.io');

const app = express();
app.use(cors());
app.use(express.json());

// Create an HTTP server and integrate Socket.io
const server = http.createServer(app);
const io = socketIo(server, {
  cors: {
    origin: "*", // Adjust this for your production domain
  }
});

// Socket.io connection event
io.on('connection', (socket) => {
  console.log('New client connected');

  // Listen for a "taskUpdated" event from any client
  socket.on('taskUpdated', (data) => {
    // Broadcast the update to all other clients
    socket.broadcast.emit('taskUpdated', data);
  });

  socket.on('disconnect', () => {
    console.log('Client disconnected');
  });
});

// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost/kanban', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
.then(() => console.log('MongoDB connected'))
.catch(err => console.log(err));

// Your CRUD routes would be here (create, update, delete tasks, etc.)

// Start the server on the HTTP server instance
const PORT = process.env.PORT || 5000;
server.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Client-Side Usage

On the client side (e.g., in your React app), you can use the Socket.io client library to listen for task updates:

import io from 'socket.io-client';

const socket = io('http://localhost:5000'); // Replace with your deployed URL

socket.on('taskUpdated', (updatedTask) => {
  // Update your state to reflect the updated task
  console.log('Task updated:', updatedTask);
});

// When a task is updated locally, emit the event:
const updateTask = async (task) => {
  // Perform your API update call first...
  await axios.put(`http://localhost:5000/tasks/${task._id}`, task);
  // Then notify other clients:
  socket.emit('taskUpdated', task);
};

Example 2: User Authentication with JWT

Adding authentication helps restrict who can modify or view data. Below is a basic example using JSON Web Tokens (JWT) and bcrypt for password hashing.

Step 1: Install Authentication Packages

npm install jsonwebtoken bcryptjs

Step 2: Create a User Model (User.js)

Define a Mongoose model for your users with hashed passwords:

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const UserSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
});

// Hash password before saving
UserSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  try {
    const salt = await bcrypt.genSalt(10);
    this.password = await bcrypt.hash(this.password, salt);
    next();
  } catch (error) {
    next(error);
  }
});

module.exports = mongoose.model('User', UserSchema);

Step 3: Add Authentication Routes in index.js

Add routes for user registration and login. On login, generate a JWT that the client can store and use for authenticated requests.

const jwt = require('jsonwebtoken');
const User = require('./User');

// Register Route
app.post('/register', async (req, res) => {
  try {
    const { email, password } = req.body;
    const user = new User({ email, password });
    await user.save();
    res.status(201).json({ message: 'User registered successfully' });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// Login Route
app.post('/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    const user = await User.findOne({ email });
    if (!user) return res.status(400).json({ error: 'User not found' });

    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) return res.status(400).json({ error: 'Invalid credentials' });

    // Generate a JWT token
    const token = jwt.sign({ id: user._id }, 'your_jwt_secret', { expiresIn: '1h' });
    res.json({ token });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Step 4: Protect Routes (Optional Middleware)

You can create middleware to protect routes that require authentication:

const authMiddleware = (req, res, next) => {
  const token = req.headers['authorization'];
  if (!token) return res.status(401).json({ error: 'No token provided' });

  jwt.verify(token, 'your_jwt_secret', (err, decoded) => {
    if (err) return res.status(401).json({ error: 'Failed to authenticate token' });
    req.userId = decoded.id;
    next();
  });
};

// Example: Protect the task creation route
app.post('/tasks', authMiddleware, async (req, res) => {
  // Only authenticated users can create tasks
  try {
    const task = new Task(req.body);
    await task.save();
    res.status(201).json(task);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

These two examples illustrate how you can add cool, practical features to your Kanban Task Board backend.

  • Real-time collaboration enhances the user experience by synchronizing updates across all connected clients.
  • User authentication secures your API and ensures that only authorized users can make changes.
Keep your curiosity going.Explore more Projects →
287 TUTORIALS · 22 TOPICSREADY