- Add MongoDB Production and Test connection URLs to /.env
npm i
npm run dev
- Will test any
*.test.ts
files npm run test:server
- Working User Register & Login System
- Typescript On Client and Server
- Dynamic Controllers via Routing-Controllers
- Service Injection via Typedi
- Shared Resource System
- Error Handling
- Logging System to output to AWS S3
- NodeJS & Express
- PassportJS
- React & Redux
- Mocha & Chai
Because the server and client both are written in TypeScript we have the ability to share code between them. The "shared" folder contains different resources and classes that are used on both sides.
By sharing code we are able to standardize communication between client and server and ensure that the data being transferred between them is valid.
I implemented a "mapping" system which takes some type of JSON data, creates a defined resource from that data, and then validates that the data is "good" by checking defined constraints. A resource is nothing more than a basic class containing data that is meant to be passed between the client and server. For example, the UserRegisterMapper contains functions to take in JSON data, verify different constraints (i.e. the email is valid, passwords match, etc.), return an error if the constraints don't pass, and build the UserRegisterResource. The functions are used differently depending on whether the mapper is being used on the client or server side.
On the server-side, data sent in from HTTP requests is passed into the mapper and validated. If the constraints dont pass it will immediately send back a 400 response with the error message. If the constraints do pass it will continue and send the built resource to the controller method.
I implemented a special decorator, @BuildResource(mapper, strict)
, to be used in controller functions that will automagically build resources from the
HTTP request with a given mapper. You can see an example of it here.
In that example we are building a UserRegisterResource
using the UserRegisterMapper
specified in the decorator. We also pass
in true
to the decorator to tell it to be strict and check all constraints. If it's false it will skip them. The decorator is not able to
leave the controller method, so the next line checks if the registerResource
is defined, and if it's not it means that the constraints did not
pass and we return the response (which contains the error set from the mapper).
The server-side is able to take a resource and it's mapper, create a special JSON object, and send it over to the client. Every mapper has a unique
ID associated with it that is passed in along with the resource. You can see an example of it here
with the HttpUtils.mappedResourceToJson(resource, mapperId)
function.
I have configured the client to detect this special JSON object and automagically build the resource on the client-side. The resource will then be passed out to
wherever the request was originally made. This is all done within the dispatch function I created
to be used when making any requests to the server.
In order for your own mapper to be detected you must add it to the array
within the MapperUtils
.
Similar to how the resource system works, HttpErrors are also shared between client and server. When an error occurs within controller
or service code on the server-side, a defined HttpError
should be created and sent to the client. The HttpError.getJson()
function will
form the JSON object to be sent to the client, and the client automatically detects it and rebuilds the error. The client-side is configured to only
pass errors out of the dispatched request that are defined. Any other errors are considered critical and will not be passed into the calling function
(you can change this if needed). This is done to ensure that whatever component made the request is going to receive an error that it knows how to handle.
When a resource is being built through the @BuildResource
decorator, if any of the constraints fail it will create a BadRequestError
and send it back
to the client. You can see the implementation here.
If you create your own errors, you will need to add an if statement here so that the client-side can detect and build the error.
The logging system offers the ability to connect to an AWS S3 bucket and output log files to it using the s3-streamlogger
package. By default it is disabled.
In order to enable it you need to add the follow variables to your environment/.env file:
LOG_S3
: True/False - Enables logging to S3S3_LOG_BUCKET_NAME
: Your S3 bucket nameAWS_ACCESS_KEY_ID
: AWS Access KeyAWS_SECRET_ACCESS_KEY
: AWS Secret Key
Whenever using the Logger
functions it will automatically write them out to S3.