This project is a quest to learn how to use TypeScript to build a restful API using node.js and express framework. While i am happy with the progress i've made, i do not think it is ready for a production environment yet, (see security), but feel free to clone and modify it to your need.
To run the project you will need the following installed on your device:
To send emails you'll need a verified SendGrid account, create a verified sender and a sendgrid API key. Currently there is no option to opt out of sending emails using sendgrid, if you wish to send emails you can try nodemailer, it will be added in the future (oh look a possible contribution ๐).
git clone <repo-url> project-name
cd project-name
npm i
Save .env.example
as .env
and fill in the following environment variables.
In your terminal running mongod
should start the mongodb server.
- To run in production mode, where all TypeScript source codes are compiled and the JavaScript output is run directly in
node
:
npm run build:compile
npm start
- To run in development mode, where all TypeScript source are recompiled whenever they are modified:
npm run dev
View the api documentation for all available routes, endpoints, and information regarding sending HTTP requests to the server, along with the server's responses.
NODE_ENV= # node.js environment
PORT= # port to listen for connections
MONGO_PATH= # mongodb connection uri
MONGO_USER= # mongodb admin name
MONGO_PASSWORD= # mongodb admin password
MONGO_DATABASE= # mongodb database name
JWT_SECRET= # secret used for signing jsonwebtokens
APP_NAME= # app name to use in emails
APP_EMAIL= # verified sendgrid email sender
SECRET_CHARACTERS= # characters used to generate users secrets
SENDGRID_API_KEY= # your sendgrid api key
SECRET_KEY= # secret key for crypto apis
SECRET_IV= # secret iv for crypto apis
USER_SECRET_TOKEN_LENGTH= # length of secret for users
Depending on the security settings of your mongodb connection just MONGO_PATH
and MONGO_DATABASE
are needed, if admin credentials are required you can modify the string used in the mongoose connection.
src/
|--middlewares/ # Custom Express middlewares.
|--resources/
|--resource/ # A singular entity representing a specific type of data or operation.
|--resource.controller.spec.ts # unit test for the controller.
|--resource.controller.ts # Controller.
|--resource.interface.ts # Interface representing the resource, for type inference.
|--resource.model.ts # Database representation of the resource.
|--resource.routes.test.ts # Integration tests for the routes.
|--resource.routes.ts # Routes.
|--resource.service.spec.ts # Unit tests for the service class.
|--resource.service.ts # Service class.
|--resource.validation.ts # Validation schemas.
|--index.ts # Array of available routers for each resource.
|--tests/ # Objects and functions that streamline testing.
|--utils/ # Utility classes and functions.
|--app.ts # Express app setup.
|--index.ts # Application's entry point.
- MongoDB: via mongoose
- Validation: via Joi
- Authentication: via jsonwebtoken
- Sending emails: via Twilio SendGrid
- One time password: via otpauth
- Logging: via pino and morgan
- Testing: using jest and supertest
- Code documentation: via TSdoc and TypeDoc
- API documentation: via postman
- Dependency management: using npm
- Environment variables: via dotenv
- Cross-origin resource sharing: enabled via cors
- Linting: using ESlint and Prettier
Name | Description |
---|---|
start | runs compiled JavaScript source from app's entry point |
dev | watches TypeScript files for changes then recompile |
build:compile | compiles TypeScript files via options in tsconfig.json |
build:clean | deletes the compiled JavaScript output folder |
build | sequentially runs build:clean and build:compile |
build:openapi | use bundle openapi files |
docs:compile | generates code documentation using TypeDoc |
docs:clean | deletes the generated documentation folder |
docs | sequentially runs docs:clean and docs:compile |
lint | lints TypeScript files using eslint |
pretty | formats TypeScript files using prettier rules |
format | sequentially runs pretty and lint |
prepare | setup Git hooks when npm install command is run |
test | runs tests using jest |
Certain routes require a valid JWT access token in the Authorization request header, using the Bearer schema. If the request does not contain a valid access token an Unauthorized (401) error is thrown.
An access token can be generated for a registered user by making a request to (POST api/v1/auth/login)
endpoint.
Open index.html in your browser to view the generated code documentation.
The API documentation is generated using postman via the OpenApi specification for this api.
You can work directly with the postman collection as well:
PS: certain texts in the request fields and response body are lorem ipsum
, they are auto generated by postman.
Jest in used for assertions and mocks while supertest is used to test the server's endpoints and routes.
Create a new .test.env file and copy the contents form .env but use specific test variables, for example a dedicated test
database, set node environment to test
, test API keys can be used etc.
Test files end with .spec.ts for unit tests and .test.ts for integration tests, see project structure.
npm test filename.test.ts
# or
npm test filename.spec.ts
npm test
The debugging environment is setup using ts-node, tsconfig-paths and TypeScript sourceMaps, see launch.json.
The debugger starts up the server, in development environment, which will listen to incoming HTTP requests and sends a response when the request is completed. Breakpoints can be set within modules and functions that handle or interacts with a specific request.
- Compile all TypeScript source code (if you haven't)
- Startup mongodb server
- Setup breakpoints
- Start the debugger
- Send HTTP request.
One reason i don't consider this project ready for production is mainly because of security, in future versions i will be implementing necessary recommendations from OWASP and express.
All errors thrown are caught by an error handling middleware function by calling the express's next function, in a request handler, and passing the error as an argument next(error)
. Errors from asynchronous operations are caught using try catch
blocks.
All HTTP requests are logged to the console using morgan.
A custom logger is available for logging messages to the console using pino.
import { logger } from '@utils/logger.util';
logger.info('message');
logger.warn('message');
logger.error('message');
logger.error(errorObject, 'message');
Currently all logs done using the logger, regardless of log levels, are stored in a app.log file within a logs folder. app.log is rotated every 7 days or if it's size reaches 300kb. In the future i'll try to save http logs from morgan and other log levels from the logger into separate log files.