TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. It offers classes, modules, and interfaces to help you build robust components. TypeScript is one of the many languages that can be used with the Node.js runtime environment.
NeDB is a lightweight NoSQL database that can be used in both Node.js and the browser. It supports the MongoDB API, making it easy to use with existing MongoDB libraries and tools.
In this article, we'll show you how to use TypeScript with NeDB. We'll create a simple to-do application that stores data in a NeDB database.
First, we need to create a TypeScript project. We'll use the TypeScript Node Starter project template.
$ git clone https://github.com/Microsoft/TypeScript-Node-Starter.git todo-app
$ cd todo-app
$ npm install
Next, we need to install the NeDB types.
$ npm install --save-dev @types/nedb
We'll start by creating a To-Do model. Create a new file called todo.ts
in the src
directory with the following code:
export class ToDo {
_id: string;
title: string;
completed: boolean;
date: Date;
constructor(title: string) {
this.title = title;
this.completed = false;
this.date = new Date();
}
}
This model has four fields: _id
, title
, completed
, and date
. The _id
field is the primary key and is generated by NeDB. The title
field is the to-do item's title. The completed
field is a boolean that indicates whether the to-do item is completed. The date
field is the date when the to-do item was created.
Next, we'll create a To-Do service. This service will be responsible for CRUD operations on To-Do items. Create a new file called todoService.ts
in the src
directory with the following code:
import { Datastore } from 'nedb';
import { ToDo } from './todo';
export class ToDoService {
private db: Datastore;
constructor(db: Datastore) {
this.db = db;
}
public async getAll(): Promise<ToDo[]> {
return new Promise<ToDo[]>((resolve, reject) => {
this.db.find({}, (err, docs: ToDo[]) => {
if (err) {
return reject(err);
}
resolve(docs);
});
});
}
public async getById(id: string): Promise<ToDo | null> {
return new Promise<ToDo | null>((resolve, reject) => {
this.db.findOne({ _id: id }, (err, doc: ToDo) => {
if (err) {
return reject(err);
}
resolve(doc);
});
});
}
public async create(todo: ToDo): Promise<string> {
return new Promise<string>((resolve, reject) => {
this.db.insert(todo, (err, doc: ToDo) => {
if (err) {
return reject(err);
}
resolve(doc._id);
});
});
}
public async update(todo: ToDo): Promise<number> {
return new Promise<number>((resolve, reject) => {
this.db.update({ _id: todo._id }, todo, {}, (err, numUpdated: number) => {
if (err) {
return reject(err);
}
resolve(numUpdated);
});
});
}
public async delete(id: string): Promise<number> {
return new Promise<number>((resolve, reject) => {
this.db.remove({ _id: id }, {}, (err, numRemoved: number) => {
if (err) {
return reject(err);
}
resolve(numRemoved);
});
});
}
}
This service has five methods: getAll
, getById
, create
, update
, and delete
. These methods map to the CRUD operations on To-Do items.
The getAll
and getById
methods return a Promise
that resolves to an array of To-Do items or a single To-Do item, respectively.
The create
, update
, and delete
methods return a Promise
that resolves to the _id
of the created To-Do item, the number of To-Do items updated, or the number of To-Do items deleted, respectively.
Next, we'll create a To-Do controller. This controller will be responsible for handling HTTP requests. Create a new file called todoController.ts
in the src
directory with the following code:
import { Request, Response } from 'express';
import { ToDoService } from './todoService';
import { ToDo } from './todo';
export class ToDoController {
private todoService: ToDoService;
constructor(todoService: ToDoService) {
this.todoService = todoService;
}
public async getAll(req: Request, res: Response) {
try {
const todos = await this.todoService.getAll();
res.json(todos);
} catch (err) {
res.status(500).send(err);
}
}
public async getById(req: Request, res: Response) {
const id = req.params.id;
try {
const todo = await this.todoService.getById(id);
if (todo) {
res.json(todo);
} else {
res.sendStatus(404);
}
} catch (err) {
res.status(500).send(err);
}
}
public async create(req: Request, res: Response) {
const todo = req.body as ToDo;
try {
const id = await this.todoService.create(todo);
res.json({ id: id });
} catch (err) {
res.status(500).send(err);
}
}
public async update(req: Request, res: Response) {
const todo = req.body as ToDo;
if (!todo._id) {
return res.status(400).send('To-Do item must have an _id');
}
try {
const numUpdated = await this.todoService.update(todo);
res.json({ numUpdated: numUpdated });
} catch (err) {
res.status(500).send(err);
}
}
public async delete(req: Request, res: Response) {
const id = req.params.id;
try {
const numDeleted = await this.todoService.delete(id);
res.json({ numDeleted: numDeleted });
} catch (err) {
res.status(500).send(err);
}
}
}
This controller has five methods: getAll
, getById
, create
, update
, and delete
. These methods map to the HTTP methods GET
, GET
/:id
, POST
, PUT
, and DELETE
, respectively.
The getAll
and getById
methods return an array of To-Do items or a single To-Do item, respectively.
The create
, update
, and delete
methods return the _id
of the created To-Do item, the number of To-Do items updated, or the number of To-Do items deleted, respectively.
Next, we'll create an Express server. Express is a web application framework for Node.js.
Create a new file called server.ts
in the src
directory with the following code:
import * as express from 'express';
import * as bodyParser from 'body-parser';
import { ToDoController } from './todoController';
import { Datastore } from 'nedb';
const app = express();
const PORT = 3000;
const db = new Datastore('todo.db');
db.loadDatabase();
const todoService = new ToDoService(db);
const todoController = new ToDoController(todoService);
app.use(bodyParser.json());
app.get('/todos', todoController.getAll);
app.get('/todos/:id', todoController.getById);
app.post('/todos', todoController.create);
app.put('/todos', todoController.update);
app.delete('/todos/:id', todoController.delete);
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
This server creates a NeDB database called todo.db
and loads it. It then creates a To-Do service and a To-Do controller.
The server configures Express to use the body-parser middleware to parse JSON bodies.
The server defines routes for the To-Do controller's methods. The app.get
route maps to the GET
method. The app.get
/:id
route maps to the GET
method with a path parameter. The app.post
route maps to the POST
method. The app.put
route maps to the PUT
method. The app.delete
route maps to the DELETE
method.
Finally, the server starts listening on port 3000.
We can test our To-Do API using cURL.
First, we'll start the server:
$ npm start
> todo-app@1.0.0 start /Users/username/projects/todo-app
> ts-node src/server.ts
Server listening on port 3000
Next, we'll use cURL to create a To-Do item:
$ curl -X POST -H "Content-Type: application/json" -d '{"title": "Buy milk"}' http://localhost:3000/todos
This cURL command sends a POST
request to the /todos
endpoint with a JSON body. The JSON body contains a To-Do item with the title Buy milk
.
We should get a response like this:
{"id": "5e1d280b04a7a22a68e0e7cd"}
This is the _id
of the To-Do item that we created. We can use this _id
to retrieve the To-Do item:
$ curl http://localhost:3000/todos/5e1d280b04a7a22a68e0e7cd
We should get a response like this:
{"_id":"5e1d280b04a7a22a68e0e7cd","title":"Buy milk","completed":false,"date":"2020-01-08T05:34:51.596Z"}
This is the To-Do item that we created. We can see that the completed
field is false
and the date
field is the date when we created the item.
Next, we'll update the To-Do item:
$ curl -X PUT -H "Content-Type: application/json" -d '{"_id": "5e1d280b04a7a22a68e0e7cd", "title": "Buy eggs", "completed": true}' http://localhost:3000/todos
This cURL command sends a PUT
request to the /todos
endpoint with a JSON body. The JSON body contains the updated To-Do item. We've changed the title to Buy eggs
and set the completed
field to true
.
We should get a response like this:
{"numUpdated": 1}
This is the number of To-Do items that were updated. We can retrieve the updated To-Do item to verify that it was updated:
$ curl http://localhost:3000/todos/5e1d280b04a7a22a68e0e7cd
We should get a response like this:
{"_id":"5e1d280b04a7a22a68e0e7cd","title":"Buy eggs","completed":true,"date":"2020-01-08T05:34:51.596Z"}
Finally, we'll delete the To-Do item:
$ curl -X DELETE http://localhost:3000/todos/5e1d280b04a7a22a68e0e7cd
This cURL command sends a DELETE
request to the /todos/:id
endpoint. We're passing the _id
of the To-Do item to delete as a path parameter.
We should get a response like this:
{"numDeleted": 1}
This is the number of To-Do items that were deleted. We can verify that the item was deleted by retrieving it:
$ curl http://localhost:3000/todos/5e1d280b04a7a22a68e0e7cd
We should get a 404 Not Found
error because the To-Do item no longer exists.
In this article, we've shown you how to use TypeScript with NeDB. We've created a simple To-Do API that stores data in a NeDB database. We've also shown you how to test the API using cURL.