memento is a capstone project that serves as a solution for the problem statement:
All teams in COMP30022 in 2019 will build a Family artifacts register for their capstone project. The problem being addressed is that almost every family has items they may want to pass down the generations. Photographs, letters, previous items such as art or jewellery, items of cultural significance are examples. The project is to build a front end to browse the items in the register, and a back end that stores the items or links to the item. Each team must choose a family that will be the client for the project. The best choice is likely to be one of the families of the team members. Discussing whether your family might be interested can be done immediately, and in the first week the team will need to choose which family. More will be said in class next Thursday.
We have chosen to make a full-stack application in Javascript/Typescript. The frontend is written in React JS and the backend is running Node JS (using the Nest.JS framework).
Installation is no trivial task, so pay close attention if you want to spin up a development environment.
There are a number of libraries and frameworks to install in order to run a development instance. This tutorial covers this process from start to finish. A summarised list is as follows:
- Git (for cloning the repository), otherwise you can download the repo as a
.zipfile - npm (Node Package Manager)
- MongoDB Community Server
- MongoDB Compass (optional) highly suggested GUI for navigating the MongoDB database.
npm is the chosen package manager for this project. It is a required depdendency.
MongoDB is a NoSQL database. It is our chosen information storage format and is great for rapid prototyping. You can find installation guides for Linux, Mac OS, and Windows here.
MongoDB Compass is a GUI that sits on top of MongoDB and allows you to see the collections and documents in your database. It is crucial for effective development. You can download it from here.
If all relevant prerequisites have been installed, we can now continue onto package installations. This repository contains two projects, one for the backend (in the backend/ directory) and one for the frontend (in the client/ directory). It is important to note that each project has its own associated package.json files that stores information related to its respective project.
-
Client Packages The client keeps all of its concerns in the
client/folder. If you want to install a package to assist with frontend development, make sure youcdto theclient/directory before installing it and so you do not install it in the root direcoty. -
Backend Packages The backend keeps all of its concerns within the root directory and
backend/. For the purposes of package installations, make sure you install backend packages to the root directory and not in theclient/directory.
- Install the backend packages by running the command
npm installin the root directory. - When the backend packages have finished installing, change your directory to the
client/subfolder and then issue the samenpm installcommand.
An environment variable is a variable whose value is set outside the program. An environment variable is made up of a name/value pair, and any number may be created and available for reference at a point in time. This helps us to separate production configuration from development configuration.
As it presently stands, there are two .env files of concern. Each sub-project (backend and frontend) gets its own .env file in their root directories.
The following .env configuration is to be placed in the root directory.
MONGO_URI=mongodb://localhost:27017/memento-test
JWT_SECRET= <your secret>
EMAIL_ENABLED=true
EMAIL_USERNAME= <email username>
EMAIL_PASSWORD= <email password>
EMAIL_HOSTNAME= <email hostname>
EMAIL_PORT= <email port>
EMAIL_FROM= <email from address>
HOST_NAME= <host name>
GRAPHQL_MAX_FILE_SIZE= <max bytes per file in request>
GRAPHQL_MAX_FILES= <max files uploadable per request>
AWS_S3_ACCESS_KEY_ID= ...
AWS_S3_SECRET_ACCESS_KEY= ...
AWS_S3_REGION_NAME= < e.g. us-east-1 >
AWS_S3_BUCKET_NAME= < e.g. memento-it-project >
CDN_HOSTNAME= < e.g. abc.cloudfront.net>
NODE_ENV= <development | production>
The following .env configuration is optional and should be located at client/.env.
REACT_APP_DEFAULT_LOGIN_EMAIL= <your default login email>
REACT_APP_DEFAULT_LOGIN_PASSWORD= <your defauly login password>
ENGINE_API_KEY= <apollo engine api key>
Of course, most things in the angle brackets < and > need to be replaced for the application to function properly, and they are ommitted here as they are sensitive information (hence the need for environment variables). If you are a contributor, examples have been provided on Slack in the #keys channel.
Most of the tedious work has been completed if you have completed the above steps. You should have MongoDB Community Server installed, and optionally MongoDB Compass. To check if your database is functional, you can use MongoDB Compass to connect to your local cluster.
- Open MongoDB Compass You should be greeted with a "Connect to Host" screen.
- Setup 'Connect to Host'
On this screen, there should be a lot of default values filled in. Of importance is hostname and port. This should match your
MONGO_URIenvironment variable. In the example confguration above,MONGO_URIcontainslocalhost:27017, so the host would be localhost, and the port would be 27017. - Click 'Connect'
- Create a memento database
Click 'Create Database'. Set the database name the one in
MONGO_URI, in this case:memento-test. Set the 'Collection Name' tousers.
By now, your development environment should be sufficiently set up. Open your terminal, navigate to the root directory and issue the command:
npm run dev
- Use
camelCasefor variable and function names
Reason: Conventional JavaScript
Bad
var FooVar;
function BarFunc() {}Good
var fooVar;
function barFunc() {}- Use
PascalCasefor class names.
Reason: This is actually fairly conventional in standard JavaScript.
Bad
class foo {}Good
class Foo {}- Use
camelCaseof class members and methods
Reason: Naturally follows from variable and function naming convention.
Bad
class Foo {
Bar: number;
Baz() {}
}Good
class Foo {
bar: number;
baz() {}
}- Use
PascalCasefor name.
Reason: Similar to class
- Use
camelCasefor members.
Reason: Similar to class
- Don't prefix with
I
Reason: Unconventional.
lib.d.tsdefines important interfaces without anI(e.g. Window, Document etc).
Bad
interface IFoo {}Good
interface Foo {}- Use
PascalCasefor name.
Reason: Similar to class
- Use
camelCasefor members.
Reason: Similar to class
- Use
PascalCasefor names
Reason: Convention followed by the TypeScript team. Namespaces are effectively just a class with static members. Class names are
PascalCase=> Namespace names arePascalCase
Bad
namespace foo {}Good
namespace Foo {}- Use
PascalCasefor enum names
Reason: Similar to Class. Is a Type.
Bad
enum color {}Good
enum Color {}- Use
PascalCasefor enum member
Reason: Convention followed by TypeScript team i.e. the language creators e.g
SyntaxKind.StringLiteral. Also helps with translation (code generation) of other languages into TypeScript.
Bad
enum Color {
red,
}Good
enum Color {
Red,
}- Prefer not to use either for explicit unavailability
Reason: these values are commonly used to keep a consistent structure between values. In TypeScript you use types to denote the structure
Bad
let foo = { x: 123, y: undefined };Good
let foo: { x: number; y?: number } = { x: 123 };- Use
undefinedin general (do consider returning an object like{valid:boolean,value?:Foo}instead)
Bad
return null;Good
return undefined;- Use
nullwhere its a part of the API or conventional
Reason: It is conventional in Node.js e.g.
errorisnullfor NodeBack style callbacks.
Bad
cb(undefined);Good
cb(null);- Use truthy check for objects being
nullorundefined
Bad
if (error === null)Good
if (error)- Use
== undefined/!= undefined(not===/!==) to check fornull/undefinedon primitives as it works for bothnull/undefinedbut not other falsy values (like'',0,false) e.g.
Bad
if (error !== null)Good
if (error != undefined)The TypeScript compiler ships with a very nice formatting language service. Whatever output it gives by default is good enough to reduce the cognitive overload on the team.
Use tsfmt to automatically format your code on the command line. Also your IDE (atom/vscode/vs/sublime) already has formatting support built-in.
Examples:
// Space before type i.e. foo:<space>string
const foo: string = "hello";- Prefer single quotes (
') unless escaping.
Reason: More JavaScript teams do this (e.g. airbnb, standard, npm, node, google/angular, facebook/react). Its easier to type (no shift needed on most keyboards). Prettier team recommends single quotes as well
Double quotes are not without merit: Allows easier copy paste of objects into JSON. Allows people to use other languages to work without changing their quote character. Allows you to use apostrophes e.g.
He's not going.. But I'd rather not deviate from where the JS Community is fairly decided.
- When you can't use double quotes, try using back ticks (`).
Reason: These generally represent the intent of complex enough strings.
- Use
2spaces. Not tabs.
Reason: More JavaScript teams do this (e.g. airbnb, idiomatic, standard, npm, node, google/angular, facebook/react). The TypeScript/VSCode teams use 4 spaces but are definitely the exception in the ecosystem.
- Use semicolons.
Reasons: Explicit semicolons helps language formatting tools give consistent results. Missing ASI (automatic semicolon insertion) can trip new devs e.g.
foo() \n (function(){})will be a single statement (not two). TC39 warning on this as well. Example teams: airbnb, idiomatic, google/angular, facebook/react, Microsoft/TypeScript.
- Annotate arrays as
foos:Foo[]instead offoos:Array<Foo>.
Reasons: Its easier to read. Its used by the TypeScript team. Makes easier to know something is an array as the mind is trained to detect
[].
Name files with camelCase. E.g. accordian.tsx, myControl.tsx, utils.ts, map.ts etc.
Reason: Conventional across many JS teams.
- Use
typewhen you might need a union or intersection:
type Foo = number | { someProperty: number }
- Use
interfacewhen you wantextendsorimplementse.g
interface Foo {
foo: string;
}
interface FooBar extends Foo {
bar: string;
}
class X implements FooBar {
foo: string;
bar: string;
}
