This project is a minimalist boilerplate for creating a Node.js application using Express.js, Passport.js for authentication, and JWT for stateless authentication. It includes support for JWT refresh tokens to ensure seamless user experience and enhanced security.
- Node.js: Server-side JavaScript runtime. π₯οΈ
- Express.js: Web framework for Node.js. π
- Passport.js: Authentication middleware for Node.js. π
- JWT (JSON Web Tokens): Stateless authentication mechanism. π‘οΈ
- Refresh Tokens: Mechanism to refresh JWT tokens without requiring the user to re-authenticate. π
- Knex.js: SQL query builder for relational databases. π οΈ
- Cron Job: Scheduler for periodic tasks in the application. β²οΈ
- Request Validation: Ensures incoming request data adheres to expected formats using Zod. βοΈ
- SQLite: Lightweight, disk-based relational database. π¦
- Dotenv: Loads environment variables from a
.env
file. π
This template is ideal for beginners as it provides a simple and clean starting point without the complexity of ORM. It uses Knex.js for SQL queries to keep things straightforward and SQLite for the database to avoid the need for a separate database server.
- A refresh token is a special kind of token that can be used to obtain a new access token after the access token has expired.
- Refresh tokens are long-lived, and they can be used to refresh the access token without requiring the user to re-authenticate.
- This mechanism improves the user experience by preventing the user from having to log in again after the access token expires.
Warning:
-
Clone the repository:
git clone https://github.com/onlyjscom/express-jwt-refresh-boilerplate.git cd express-jwt-refresh-boilerplate
-
Install dependencies:
npm install
-
Create a
.env
file in the root directory and add the following environment variables:PORT=3000 JWT_SECRET_AT=your_jwt_secret JWT_SECRET_RT=your_jwt_refresh_secret
-
Run the database migrations:
npm run db:migrate
-
Start the server:
npm start
express-jwt-refresh-boilerplate
βββ src
β βββ database
β β βββ index.ts
β β βββ instance.ts
β β βββ migrations
β β β βββ 20240716211511_create_users_table.ts
β β β βββ 20240716211512_create_refresh_tokens_table.ts
β β β βββ 20240716211513_create_posts_table.ts
β β βββ parsers.ts
β β βββ tables.ts
β βββ index.ts
β βββ middlewares
β β βββ authenticate.ts
β β βββ index.ts
β β βββ validate-request.ts
β βββ modules
β β βββ auth
β β β βββ controller.ts
β β β βββ index.ts
β β β βββ passport.ts
β β β βββ router.ts
β β β βββ request-schemas.ts
β β β βββ service.ts
β β β βββ types.ts
β β βββ posts
β β β βββ controller.ts
β β β βββ index.ts
β β β βββ router.ts
β β β βββ request-schemas.ts
β β β βββ service.ts
β β β βββ types.ts
β β β βββ field-validations.ts
β β βββ users
β β βββ controller.ts
β β βββ index.ts
β β βββ router.ts
β β βββ request-schemas.ts
β β βββ service.ts
β β βββ types.ts
β β βββ field-validations.ts
β βββ tasks
β β βββ auth.ts
β β βββ index.ts
β βββ utils
β βββ common-field-validations.ts
β βββ date-helpers.ts
β βββ http-errors.ts
β βββ index.ts
β βββ string-helpers.ts
βββ db.sqlite
βββ knexfile.ts
βββ package.json
βββ tsconfig.json
You can import the Postman collection file in the repository root directory to test the API endpoints. Alternatively, you can use the following documentation to interact with the API.
Endpoint: POST /api/auth/register
Registers a new user.
Request Body:
{
"username": "exampleuser",
"password": "examplePassword",
"firstName": "John",
"lastName": "Doe",
"role": "user"
}
"role" field is optional. Available roles are "user" and "admin". Default role is "user" if not provided.
Response:
{
"user": {
"id": 1,
"username": "exampleuser",
"firstName": "John",
"lastName": "Doe",
"role": "user",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T21:15:00.000Z"
},
"accessToken": "jwtAccessToken",
"refreshToken": "jwtRefreshToken"
}
Endpoint: POST /api/auth/login
Logs in an existing user.
Request Body:
{
"username": "exampleuser",
"password": "examplePassword"
}
Response:
{
"user": {
"id": 1,
"username": "exampleuser",
"firstName": "John",
"lastName": "Doe",
"role": "user",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T21:15:00.000Z"
},
"accessToken": "jwtAccessToken",
"refreshToken": "jwtRefreshToken"
}
Endpoint: GET /api/auth/me
Retrieves the current authenticated user's information.
Request Headers:
Authorization: Bearer jwtAccessToken
Response:
{
"id": 1,
"username": "exampleuser",
"firstName": "John",
"lastName": "Doe",
"role": "user",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T21:15:00.000Z"
}
Endpoint: POST /api/auth/refresh
Refreshes the JWT access token using the refresh token.
Request Headers:
Authorization: Bearer jwtRefreshToken
Response:
{
"user": {
"id": 1,
"username": "exampleuser",
"firstName": "John",
"lastName": "Doe",
"role": "user",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T21:15:00.000Z"
},
"accessToken": "newJwtAccessToken",
"refreshToken": "newJwtRefreshToken"
}
Endpoint: POST /api/auth/logout
Logs out the current user and revokes their refresh token.
Request Headers:
Authorization: Bearer jwtRefreshToken
Response:
{
"message": "Logged out"
}
Endpoint: POST /api/auth/logout-all
Logs out the current user from all sessions and revokes all their refresh tokens.
Request Headers:
Authorization: Bearer jwtRefreshToken
Response:
{
"message": "Logged out all sessions"
}
Endpoint: GET /api/users
Retrieves a list of all users.
Query Parameters:
role
: Filter users by role. Available roles are "user" and "admin". (optional)
Response:
[
{
"id": 1,
"username": "exampleuser",
"firstName": "John",
"lastName": "Doe",
"role": "user",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T21:15:00.000Z"
},
{
"id": 2,
"username": "verissimus",
"firstName": "Marcus",
"lastName": "Aurelius",
"role": "admin",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T22:00:00.000Z"
}
]
Endpoint: GET /api/users/:id
Retrieves the details of a user by their ID.
Request Parameters:
id: User's ID (integer)
Response:
{
"id": 1,
"username": "exampleuser",
"firstName": "John",
"lastName": "Doe",
"role": "user",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T21:15:00.000Z"
}
Endpoint: PUT /api/users/:id
Updates the details of a user by their ID.
Request Parameters:
id: User's ID (integer)
Request Headers:
Authorization: Bearer jwtAccessToken
Request Body:
{
"username": "newUsername",
"password": "newPassword",
"firstName": "newFirstName",
"lastName": "newLastName",
"role": "user"
}
All fields are optional.
Response:
{
"id": 1,
"username": "newusername",
"firstName": "Newfirstname",
"lastName": "Newlastname",
"role": "user",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T23:35:00.000Z"
}
Endpoint: DELETE /api/users/:id
Deletes a user by their ID.
Request Parameters:
id: User's ID (integer)
Request Headers:
Authorization: Bearer jwtAccessToken
Response:
{
"message": "User deleted"
}
Endpoint: GET /api/posts
Retrieves a list of all posts.
Query Parameters:
userId
: Filter posts by user ID. (optional)
Response:
[
{
"id": 1,
"userId": 1,
"title": "Post Title 1",
"content": "Post content 1",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T21:15:00.000Z"
},
{
"id": 2,
"userId": 1,
"title": "Post Title 2",
"content": "Post content 2",
"createdAt": "2024-07-16T22:00:00.000Z",
"updatedAt": "2024-07-16T22:15:00.000Z"
},
{
"id": 3,
"userId": 2,
"title": "Post Title 3",
"content": "Post content 3",
"createdAt": "2024-07-17T15:15:00.000Z",
"updatedAt": "2024-07-17T15:15:00.000Z"
}
]
Endpoint: GET /api/posts/:id
Retrieves a single post by its ID.
Request Parameters:
id
: The ID of the post to retrieve.
Response:
{
"id": 1,
"userId": 1,
"title": "Post Title",
"content": "Post content",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T21:15:00.000Z"
}
Endpoint: POST /api/posts
Creates a new post.
Request Headers:
Authorization: Bearer jwtAccessToken
Request Body:
{
"title": "New Post Title",
"content": "New Post Content"
}
Response:
{
"id": 1,
"userId": 1,
"title": "New Post Title",
"content": "New Post Content",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T21:15:00.000Z"
}
Endpoint: PUT /api/posts/:id
Updates an existing post by its ID.
Request Headers:
Authorization: Bearer jwtAccessToken
Request Parameters:
id
: The ID of the post to update.
Request Body:
{
"title": "Updated Post Title",
"content": "Updated Post Content"
}
All fields are optional.
Response:
{
"id": 1,
"userId": 1,
"title": "Updated Post Title",
"content": "Updated Post Content",
"createdAt": "2024-07-16T21:15:00.000Z",
"updatedAt": "2024-07-16T21:15:00.000Z"
}
Endpoint: DELETE /api/posts/:id
Deletes a post by its ID.
Request Headers:
Authorization: Bearer jwtAccessToken
Request Parameters:
id
: The ID of the post to delete.
Response:
{
"message": "Post deleted"
}
To protect routes, use the authenticate
middleware:
import { authenticate } from '../../middlewares';
app.get('/protected-route', authenticate, (req, res) => {
res.json({ message: 'This is a protected route' });
});
This middleware ensures that the request is authenticated using the JWT access token. If the token is valid, the user information is attached to the request object, and the request is passed to the next middleware or route handler.
To validate requests, use the validateRequest
middleware:
import { authenticate, validateRequest } from '../../middlewares';
import { postCreationRequestSchema } from './schemas';
app.post('/posts', [validateRequest(postCreationRequestSchema), authenticate], (req, res) => {
// Handle post creation
});
This middleware uses Zod schemas to validate the incoming request data and ensures that only valid data is processed further.
- Fork the repository.
- Create your feature branch:
git checkout -b feature/your-feature-name
. - Commit your changes:
git commit -m 'Add some feature'
. - Push to the branch:
git push origin feature/your-feature-name
. - Open a pull request.
This project is licensed under the MIT License. See the LICENSE file for details.
Special thanks to the authors of the libraries and tools used in this project. Your work is greatly appreciated!