Node.js: Task API Project (Part 1)

Last updated: 2026-08-26

1. Project Launch: Alice's First Day

Alice and her team received a new request—to build a team task management API. On the first day, they decided to start by setting up the project framework and the user authentication module. “Authentication is the foundation of everything,” Alice said. “Without authentication, the rest of the task management system would be meaningless.”



2. Project Initialization and Directory Structure

▶ Example: (1) Initialize an Express project

BASH
mkdir task-manager-api && cd task-manager-api
npm init -y
npm install express mongoose bcryptjs jsonwebtoken dotenv cors helmet
npm install --save-dev nodemon

▶ Example: (2) Directory Structure Design

TEXT 📖 Display only
task-manager-api/
├── src/
│   ├── config/
│   │   └── db.js
│   ├── middleware/
│   │   └── auth.js
│   ├── models/
│   │   ├── User.js
│   │   └── Task.js
│   ├── routes/
│   │   ├── auth.js
│   │   └── tasks.js
│   ├── validators/
│   │   └── authValidator.js
│   └── app.js
├── .env
├── .gitignore
├── package.json
└── server.js
Directory/File Description
src/config/ Database connection, environment variables, and other configurations
src/middleware/ Middleware for authentication, error handling, etc.
src/models/ Mongoose Schema and Model Definitions
src/routes/ Routing Modules, Categorized by Function
src/validators/ Request Parameter Validation Logic
src/app.js Express Main Application File
server.js Input file, start the server

▶ Example: server.js entry file

JAVASCRIPT
require('dotenv').config();
const app = require('./src/app');
const connectDB = require('./src/config/db');

connectDB();

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

▶ Example: Database Connection Configuration

JAVASCRIPT
const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URI);
    console.log('MongoDB connected');
  } catch (err) {
    console.error('MongoDB connection error:', err.message);
    process.exit(1);
  }
};

module.exports = connectDB;
▶ Try it Yourself

3. Mongoose Model Design

(1) User Model

Field Type Required Description
username String Yes Username, unique index
email String Yes Email, unique index
password String Yes bcrypt-hashed password
role String No Role: user (default) / admin
createdAt Date Automatic Creation Date

(2) Task Model

Field Type Required Description
title String Yes Task Title
description String No Task Details
status String No pending (default) / in-progress / completed
priority String No low (default) / medium / high
assignedTo ObjectId Yes Assigned user, associated with User
dueDate Date No Deadline
createdAt Date Automatic Creation Date
updatedAt Date Automatic Last Updated

▶ Example: User Model Definition

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

const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true, trim: true },
  email: { type: String, required: true, unique: true, lowercase: true },
  password: { type: String, required: true, minlength: 6 },
  role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });

userSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 10);
  next();
});

userSchema.methods.comparePassword = function (candidate) {
  return bcrypt.compare(candidate, this.password);
};

module.exports = mongoose.model('User', userSchema);
▶ Try it Yourself

▶ Example: Task Model Definition

JAVASCRIPT
const mongoose = require('mongoose');

const taskSchema = new mongoose.Schema({
  title: { type: String, required: true, trim: true },
  description: { type: String, default: '' },
  status: { type: String, enum: ['pending', 'in-progress', 'completed'], default: 'pending' },
  priority: { type: String, enum: ['low', 'medium', 'high'], default: 'low' },
  assignedTo: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  dueDate: { type: Date }
}, { timestamps: true });

module.exports = mongoose.model('Task', taskSchema);
▶ Try it Yourself

4. User Authentication Module

▶ Example: (1) How JWT Authentication Works

100%
graph TD
    A[User Registration/Log In] --> B[Server Authentication Credentials]
    B --> C[Generate JWT Token]
    C --> D[Back Token To the client]
    D --> E[Client-side Token Request]
    E --> F[auth Middleware Validation Token]
    F -->|Valid| G[Forward to the routing processor]
    F -->|Invalid| H[Back 401 Error]

(2) Design of Authentication API Endpoints

Method Path Description Authentication Required
POST /api/auth/register User Registration No
POST /api/auth/login User Login No
GET /api/auth/me Get current user Yes

▶ Example: Registration and Login Routes

JAVASCRIPT
const router = require('express').Router();
const jwt = require('jsonwebtoken');
const User = require('../models/User');

const generateToken = (id) => jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '7d' });

router.post('/register', async (req, res, next) => {
  try {
    const { username, email, password } = req.body;
    const user = await User.create({ username, email, password });
    res.status(201).json({ token: generateToken(user._id), user: { id: user._id, username, email, role: user.role } });
  } catch (err) {
    next(err);
  }
});

router.post('/login', async (req, res, next) => {
  try {
    const { email, password } = req.body;
    const user = await User.findOne({ email });
    if (!user || !(await user.comparePassword(password))) {
      return res.status(401).json({ message: 'Invalid credentials' });
    }
    res.json({ token: generateToken(user._id), user: { id: user._id, username: user.username, email, role: user.role } });
  } catch (err) {
    next(err);
  }
});

module.exports = router;
▶ Try it Yourself

▶ Example: auth middleware

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

module.exports = async (req, res, next) => {
  const header = req.headers.authorization;
  if (!header || !header.startsWith('Bearer ')) {
    return res.status(401).json({ message: 'No token provided' });
  }
  try {
    const decoded = jwt.verify(header.split(' ')[1], process.env.JWT_SECRET);
    req.user = await User.findById(decoded.id).select('-password');
    if (!req.user) return res.status(401).json({ message: 'User not found' });
    next();
  } catch {
    res.status(401).json({ message: 'Invalid token' });
  }
};
▶ Try it Yourself

▶ Example: Get the current user's route

JAVASCRIPT
const auth = require('../middleware/auth');

router.get('/me', auth, async (req, res) => {
  res.json({ user: { id: req.user._id, username: req.user.username, email: req.user.email, role: req.user.role } });
});
▶ Try it Yourself

5. The app.js Main File and .env Configuration

▶ Example: Integrating app.js

JAVASCRIPT
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const authRoutes = require('./routes/auth');

const app = express();

app.use(helmet());
app.use(cors());
app.use(express.json());

app.use('/api/auth', authRoutes);

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.statusCode || 500).json({ message: err.message || 'Server Error' });
});

module.exports = app;
▶ Try it Yourself

▶ Example: .env Environment Variables

TEXT 📖 Display only
PORT=3000
MONGO_URI=mongodb://localhost:27017/task-manager
JWT_SECRET=your_super_secret_key_change_in_production


6. Comprehensive Example: The Complete Project Initialization Process

The following code ties all the core modules mentioned above together. With about 80 lines of core code, you can run a basic task management API with authentication:

JAVASCRIPT
// server.js
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const helmet = require('helmet');

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

// --- Models ---
const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });
userSchema.pre('save', async function () { if (this.isModified('password')) this.password = await bcrypt.hash(this.password, 10); });
userSchema.methods.comparePassword = function (pw) { return bcrypt.compare(pw, this.password); };
const User = mongoose.model('User', userSchema);

// --- Auth Middleware ---
const auth = async (req, res, next) => {
  try {
    const decoded = jwt.verify(req.headers.authorization?.split(' ')[1], process.env.JWT_SECRET);
    req.user = await User.findById(decoded.id).select('-password');
    next();
  } catch { res.status(401).json({ message: 'Unauthorized' }); }
};

// --- Routes ---
app.post('/api/auth/register', async (req, res) => {
  const user = await User.create(req.body);
  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
  res.status(201).json({ token, user: { id: user._id, username: user.username, role: user.role } });
});
app.post('/api/auth/login', async (req, res) => {
  const user = await User.findOne({ email: req.body.email });
  if (!user || !(await user.comparePassword(req.body.password))) return res.status(401).json({ message: 'Invalid credentials' });
  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
  res.json({ token, user: { id: user._id, username: user.username, role: user.role } });
});
app.get('/api/auth/me', auth, (req, res) => res.json(req.user));

// --- Start ---
mongoose.connect(process.env.MONGO_URI).then(() => {
  app.listen(process.env.PORT || 3000, () => console.log('Server running'));
});

❓ FAQ

Q How should the project directory structure be designed?
A Organize it by functionality: store data models in models/, routes in routes/, middleware in middleware/, and configurations in config/. Place the entry point, app.js, in the root directory.
Q Why use Mongoose instead of the native driver?
A Mongoose provides schema validation, middleware hooks, type hints, and a query builder, which boosts development efficiency; the native driver is lighter and better suited for simple scenarios.
Q Should the .env file be committed to Git?
A Absolutely not. The .env file contains sensitive information such as database passwords and keys, so it should be added to .gitignore and injected via environment variables or CI during deployment.
Q How long should the JWT secret be?
A It should be a random string of at least 32 characters; for production environments, 64 characters or more is recommended. You can generate one using node -e "console.log(require('crypto').randomBytes(64).toString('hex'))".
Q How do I test the registration API?
A Use Postman or curl to send a POST request to /api/auth/register, check the returned token, and then use that token to access a protected route to verify that authentication is working.

📖 Summary

📝 Exercises

  1. Initialize the project according to the directory structure, install all dependencies, and ensure that npm run dev can start the server.
  2. Create two Mongoose models, User and Task, and use Postman to test the registration and login APIs.
  3. Write an auth middleware and test the /api/auth/me endpoint: It should return a 401 error if no token is provided, and return the user's information if a valid token is provided.
  4. Configure JWT_SECRET and MONGO_URI in .env, and ignore the .env file in .gitignore.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏