- Notifications
You must be signed in to change notification settings - Fork86
Build a real fullstack app (backend+website+mobile) in 100% Typescript
License
shanhuiyang/TypeScript-MERN-Starter
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Live Demo:https://hd283.net/
Supported Clients | Preview |
---|---|
Web | ![]() |
Web (Mobile) | ![]() ![]() ![]() |
Android | ![]() ![]() ![]() |
iOS | ![]() ![]() ![]() |
This project built a real blog app for all platforms in TypeScript.
TypeScript is a typed super set of JavaScript.If you are new to TypeScript it is highly recommended to become familiar with it first before proceeding.You can check out its documentationhere.
MERN stands forMongoDB,Express.js,React/ReactNative, andNode.js.
TypeScript has brought the following benefits to MERN:
- Unified modeling across web client and server for objects
- Type safety, and easy refactoring of typed code across web client and server
- A superior developer experience in a team environment
Not only using TypeScript, but this project is also featured by:
- Real fullstack, because you can build nodeserver, Mongo DB, multimedia storage,website (for both desktop and mobile),Android app, andiOS app based on this projectin single programming language.
- RESTful-style. Powered by an embeddedoauth2 server andpassport.js, this project separate client and server clearly. Then the REST server can serve for both website and mobile clients simultaneously.
- React-router 4.0+, with it you can easily define client routes and manage them.
- Redux, with it you can easily manage client states. Alsoall clients shared the same state machine implemented by Redux, we reduced a lot of effort when you develop for multi-clients.
- Almost ready for a blog app. We modeled
User
as well asArticle
. This is areal starter for who would like to build a blog app or extend it to a community app using MERN. - The web client code is created fromcreate-react-app, so now you can get rid of annoying configurations for babel and webpack.
- The Android/iOS client is created fromExpo, which is a tool encapsulated a lot of complexity when you develop on ReactNative.
- Quick start
- Motivation
- Project structure
- How to debug
- Tests
- Deploying the app
- Prerequisite
- Create production MongoDB (Option 1: Azure CosmosDB)
- Create production MongoDB (Option 2: MongoDB Atlas)
- Create a production blob storage
- Test the application in production mode
- Build app into a Docker image
- Create an Azure app service using Docker image
- Map custom domain and bind SSL certificate
- Deploy the mobile apps
- Summary of deployment
To build and run this app locally you will need a few things:
git clone https://github.com/shanhuiyang/TypeScript-MERN-Starter.git<project_name>
cd<project_name>yarn
(you'll probably have to start another command prompt)
mongod
Note! While you just installed MongoDB (for both on OSX or Windows),it may remind you that you can run MongoDBas service,then your MongoDB will always be running even after you restart your computer.
yarn start
Finally, navigate tohttp://localhost:3000 and you should see the blog page being served and rendered locally!
Note! Youcannot open the localhost website on IE and Edge browser.In contrast, you can open it on Safari, Firefox, and Chrome.However, we will ensure the compatibility of the production app on IE and Edge browser.
The commandyarn start
will also start the react-native project using expo-cli.So you can navigate tohttp://localhost:19002 and you will see the Expo DevTool page.From that page you can easily start your app for both Android and iOS by clicking corresponding buttons.
For more detail of prerequisite please refer toInstallation of Expo document.
Briefly speaking, you should prepare for devices you would like to debug on, either emulator/simulator or physical devices.
[TODO] Elaborate this part.
Note! Currently you cannot test Android app on emulator without configure the host url manually.This is because in Android emulator you cannot access
http://localhost
which hosted on the same server/PC/Mac.You can test your app in the same LAN, see next section for detail.
By default the request url connect client and service arehttp://localhost
, i.e. you can only test client and service in the same server/PC/Mac.
To test your app in LAN, you can do following before enter commandyarn start
.
- Get the IP address (either global IP or LAN IP) of your development server.
- Modify the constant
HOST_NAME_DEV
inclient/core/src/models/HostUrl.ts with the IP address you got.
This project implement the auth service, as well as account verification by sending OTP to user's registered email account.
By default the account verification is turned off since SMTP transporter is not configured with account and password.
To test this part of functionality, you can:
- add your email account and password toserver/config/smtp-transporter.ts.
- change the flag to
true
inclient/core/src/shared/constants.ts.
Up to 2020, React is the most popular front-end framework.For novice web developers, if they would like to build a full-stack project using Javascript, they can intuitively choose the combination of Node.js and React.
Typescript is a success Javascript alternative which can keep the maintainability as the project grows.The most famous Typescript project isAngular.Nowadays React alsosupport Typescript officially.
Therefore, the motivation of this project is building a fullstack web app using Typescript, then we can maintain and extend a web app easily.
On the other hand, we have seen the template like TypeScript-Node-Starter, TypeScript-React-Starter, and TypeScript-React-Native-Starter.What if we mixed these 3 projects together?How could Typescript shows its advantage if we use it across client & server?So this project is intended to explore the capability of Typescript in that circumstance.
mern-starter is a famous project which combine React and Node.js in a single project.However, it is deprecated after April, 2019.This project is a server rendering solution.It modeled thePost
object, but did not implement the authentication module.
TypeScript-Node-Starter is an officially built starter project for Node.js by Microsoft.This project is a good starter for Node.js app using Typescript.It is also a server rendering solution, usingpug.
IndeedTypeScript-MERN-Starter is built on top of TypeScript-Node-Starter.However, as you can see inTypeScript-MERN-Starter, the architecture has been totally changed comparing to TypeScript-Node-Starter.
oauth2api is a good example on how to implement OAuth2 strategy in an Node.js app.We almost totally imported this project toTypeScript-MERN-Starter.
We build this project on following philosophies:
- Beginner friendly. You can start the app without any complex configuration for build and runtime.
- Easy to extend. You can always focus on your business logic. We chose well-documented 3rd party packages to use, so that you can extend this app easily by following the working patterns of these packages.
- Share the code as much as possible. For example, usually we prefer the 3rd party package who can be used both in React and ReactNative, such as ReactRouter, ReactIntl, etc. In contrast, we gave up to use ReactNavigation, expo-localization which can only used in ReactNative.
We have already extended this project from MERN to MERRN, where the additional R stands forReactNative.
In next period of time (Before end of Feb 2020), we will work on:
- Better documents.
- Use types as many as possible.
Use Promises instead of callbacks.Globalization. The text strings should be well managed and easily be extended for multi-languages.Better error alert.- Tests. The core of client (such as actions, reducers), and server (such as routes, controllers) should be well covered by unit tests.
- Better article UX.
Using the list-detail structure.User can add comments to articles. (website only)Pagination. (website only)
Rich text editors. Using markdown as the exchanging text format.Unsaved alert if users are going to close the page.
Notification (website only)Comment received for article authors.
Improving the authentication. (website only)Email verification for signing up.Password modification.Forget password.
User-friend time display.- Incoming issues.
In this part, we will not only summarize the folder structure, but also introduce how each of the project gradients works.
Briefly speak, we nested 3 projects in the repo, to get the biggest reusability of code.
Therefore, we fused server, website (for both desktop and mobile), and Android & iOS projects together in this repo.
The basic working flow of the app could be illustrated in the following diagram.
Please note that we did not invent any framework, what we did is taking the advantages of many popular frameworks from React ecosystem, and organize it logically.
Here is a index fordocuments what the project depends on:
- Create React App
- Expo
- Express
- Jest
- MongoDB
- Mongoose
- NativeBase
- Node
- PassportJS
- React
- ReactNative
- ReactRouter
- Redux
- Semantic-UI React
- Typescript
. // a complete nodeJS/Express project├── .vscode // VS Code specific settings|├── client // a complete Expo/ReactNative project: all of the client code| || ├── core // a complete create-react-app project: all of the website code| | ├── build // output from your TypeScript build for website| | ├── node_modules // all of your npm dependencies for website code| | ├── public // static assets that will be used to build website| | ├── src // non-view layer code for both clients, and view layer code of the website| | | ├── actions // Redux actions implementations, shared for both website and mobile apps| | | ├── models // type definitions, may be used for both server and clients| | | ├── reducers // Redux reducers implementations, shared for both website and mobile apps| | | ├── shared // common utilities for client, shared for both website and mobile apps| | | ├── website // all view layer code of the website| | | ├── index.tsx // entry point of the website| | | ├── serviceWorker.ts // create-react-app generated file, which is used for debug| | | └── setupProxy.js // bypasses all REST API calls to nodejs server while debugging| | ├── package.json // contains npm dependencies and build scripts for website| | └── tsconfig.json // config settings for compiling website| || ├── assets // images used by expo/react-native project| ├── src // all view layer code of the mobile apps| ├── app.json // Expo configuration for mobile apps| ├── App.tsx // entry point of the Expo/ReactNative project| ├── package.json // contains npm dependencies and build scripts for mobile apps| └── tsconfig.json // config settings for compiling mobile apps|├── dist // output from your TypeScript build for node server├── images // README.md referenced images├── node_modules // npm dependencies for server side├── server // all of the source code for server| ├── config // authorizations and emailer configurations| ├── controllers // controllers respond to various http requests| ├── models // server specific type definitions, and DB schemas| ├── repository // cloud or local file storage interfaces| ├── routes // server routing configurations| ├── translations // international strings used by server only| ├── util // utility functions| ├── app.ts // server initialization code| └── server.ts // entry point of server├── .dockerignore // defines which files or folders cannot be included for docker build├── .env.development // defines environment variables while NODE_ENV is development├── .env.production // defines environment variables while NODE_ENV is production├── .travis.yml // to configure Travis CI build├── package.json // npm dependencies and build scripts for the whole project├── start-client.js // script used to invoke build/start scripts in website project├── start-mobile.js // script used to invoke build/start scripts in mobile apps project├── tsconfig.json // config settings for compiling server code written in TypeScript├── tslint.json // config settings for TSLint code style checking for server└── yarn.lock // in which Yarnpkg stores versions of each dependency
Theclient/core folder includes a complete React app which was initialized byCreate React App.Create React App is a great tool for beginner since it is:
- Less to Learn. You don't need to learn and configure many build tools. Instant reloads help you focus on development. When it's time to deploy, your bundles are optimized automatically.
- Only One Dependency. Your app only needs one build dependency. Under the hood, it uses Webpack, Babel, ESLint, andtsc (Typescript compiler) to power on your app. Then you can get rid of the complicated configurations for them.
In production build environment,react-scripts
, the tool for use withCreate React App
will compile all of your (Typescript) source code and resources into bundles and assets inclient/core/build folder.
For all routes except those for REST APIs, the Node.js server can responseclient/core/build/static/index.html.
That is to say, our server routes all REST APIs, and leave all other possible routes to client.Server will never make up the client app, it will not decide how would the client should look like.This isthe first key point to make the RESTful architecture possible.
However, in development environment, things become totally different.When you run the commandyarn start
inclient/core folder,react-scripts
will boot a local debug server for you.This server is implemented inclient/core/src/serviceWorker.ts.It makes the hot-reload and other functions for a better development experience.
Then you have 2 servers when you are developing and debugging.One is the Node.js server developed by yourself, another is the serviceWorker booted by Create React App.The former serves REST APIs for you, the later serves React app for you.
How could we make these 2 servers work together harmoniously?
Suggested by thedocument of Create React App, we can configure the serviceWorker proxy REST API requests to the Node.js server.Conveniently, this also avoidsCORS issues.The article presented byAnthony Accomazzo clearly illustrated how this mechanism works.
We can summarize how we applied this strategy as below:
- Assign port 3000 to the serviceWorker, and assign port 3001 to the Node.js server.
- When we are running
yarn start
in project root path, these 2 servers will be started concurrently. - All client requests are going to the port 3000, i.e. the serviceWorker.
- In the fileclient/core/src/setupProxy.js, we configure that uri starts with
/api
,/auth
, and/oauth
should be proxied to port 3001., i.e. the Node.js server will handle these requests forwarded by the serviceWorker.
A big advantage of Node.js is that it can be used as a single programming language for both front and back end.However, since JavaScript is a loosely typed or a dynamic language, variables in JavaScript are not directly associated with any particular value type, and any variable can be assigned (and re-assigned) values of all types.Therefore, sometimes when you look at a function call, you may have no idea what kind of object the argument passes.Information is lost when you and your teammates are communicating using the Javascript code.
Typescript provided a good solution for large scale project of Javascript because of its type system.We took advantages of this type system to model the objects which can be used in both client and server at the same time.
Let's take a close examine.Inclient/core/src/models/UnifiedModel.d.ts, we defined a interface named UnifiedModel.This interface represent an abstract type that would be used in client, service, andMongoDB.It contains fields generated by MongoDB such as timestamps of create/update, and MongoDB ids.These fields should be read-only in client side Typescript code.
Suppose we are going to define a type named User, which contains the profile and password of a website user who can login, post an article, and view his/her profile.We will do following:
- Add a interface named
User
inclient/core/src/models/User.ts, and extends it fromUnifiedModel
. - Fill the
User
with fields you desire such as name and address, etc. - Inserver/models/User/UserDocument.d.ts, define a interface named
UserDocument
, which extendsUser
andmongoose.Document
simultaneously. This interface represent a document which can be create, read, update and delete from MongoDB. - Add the
userSchema
and necessary middleware according to the requirements ofmongoose
inserver/models/User/UserCollection.ts.
Note! The reason why we defined the common interface underclient/core folder but not a root levelmodels folder under project root is because of the restriction from Create React App and Expo.These tools cannot read the type definition out of it's workspace, i.e. theclient/core andclient folder.So we have to put our common models inclient/core folder first.Then the Node and Expo project can both access the models.
In summary,User
is the common type used on both sides of an API request.In client, the React app can render anUser
object, post anUser
object to, and receive anUser
object from server.In server, the Node.js app can CRUDUser
objects from and to MongoDB.When you are going to add a field in yourUser
definition, you may have to change both sides implementation related toUser
.If you forget to make necessary change on both sides, VS Code will kindly remind you when you are building the project.
You would feel inconvenient that after you defined theUser
interface like this,
exportdefaultinterfaceUserextendsUnifiedModel{email:string;password?:string;name:string;gender:Gender;avatarUrl:string;address?:string;website?:string;}
you still have to defineuserSchema
forMongoose
like following,
exportconstuserSchema:Schema=newmongoose.Schema({email:{type:String,unique:true},password:String,name:String,gender:String,avatarUrl:String,address:String,website:String},{timestamps:true});
Yes, it is duplicated works you need to take care about.Now you have to make sure these 2 definitions consistent after you change one of them.There is a known library namedtypegoose which is addressing the duplicated definitions problem.However since it defines the model using class extended from aTypegoose
class which contains much more required fields than we want, we cannot use that type definition in client side.We are looking forward its evolving so that we can resolve this issue in future.
To protect REST API against unauthorized request, we usedPassport in this project.Passport is an authentication middleware for Express.It is designed to serve authenticating requests.Suppose you are going to create an article in the web app, the server protected such requests like this:
article.route("/create").post(passport.authenticate("bearer",{session:false}),controllers.create);
The purpose of this statement is straightforward. It means that when a POST request with urlapi/article/create
is coming, do the following in order.
- Authorize the request with Bearer strategy, just see if it has a valid Bearer token. If yes, go to step 2; if no, return a
401 Unauthorized
error. - Handle the request using controller of article creating.
Therefore when you confirm that an REST API should be protected, just insert a single line in its route configuration like this:
passport.authenticate("bearer",{session:false}),
Let's take a look at what we did in this project to make life easier.
OAuth 2.0 (formally specified byRFC 6749) provides an authorization framework which allows users to authorize access to third-party applications.When authorized, the application is issued a token to use as an authentication credential.This has two primary security benefits:
- The application does not need to store the user's username and password.
- The token can have a restricted scope (for example: read-only access).
These benefits are particularly important for ensuring the security of web applications, making OAuth 2.0 the predominant standard for API authentication.
OAuth 2.0 defines four roles:
- resource owner.An entity capable of granting access to a protected resource.Usually the resource owner is a person, it is referred to as an application end-user.
- resource server.The server hosting the protected resources, capable of accepting and responding to protected resource requests using access tokens.
- client.An application making protected resource requests on behalf of the resource owner and with its authorization. Client usually is an application executes in your browser, on a desktop, or mobile devices.
- authorization server.The server issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorization.
In this project,resource owner is our end user.We implemented other 3 roles in Typescript.The client React app takes the role ofclient.The Node.js server takes the role ofresource server andauthorization server.
As aresource server, the Node.js server owns articles written by user.Also it can askauthorization server to authenticate the request send byclient.Ifauthorization server says this is authorized request,resource server will start its own jobs such as updating an article.
Folder Path | Description |
---|---|
server/config/passport-consumer.ts | We name it suffixed with consumer because it's the access token's consumer. In this file theresource server announces that it will askauthorization server for authorizing withOAuth2 strategy. |
server/config/oauth2orize-strategy.ts | In this file we add an adapter to theOAuth2 strategy. This adapter is used to transform the user profile object fromauthorization server toresource server . |
server/routes/auth.ts | Defines routes start with/auth/oauth2 , these routes configuresresource server on how to interact withauthorization server. You can add 3rd party authentication routes here like/auth/facebook ,/auth/twitter , etc. All the routes for role ofresource server should start with/auth |
server/controller/article.ts | CRUD operations of article. In this file theresource server does its own jobs. |
server/controller/auth.ts | Defines route controllers for/auth/oauth2/callback . This controller handles the callback fromauthorization server. In this callback theresource server can save the user information in its own DB, and forward the user information as well as access token to theclient. You can add more callback controllers here for other 3rd partyauthorization server, such as Facebook. Then the routes for the callback should be like/auth/facebook/callback . |
Note! In our DB the collection named
User
is owned byauthorization server exclusively.In the callback controller ofresource server the user account is not saved into the/anUser
collection.If you would like to import a 3rd party token provider such as Facebook, you need to create a collection likeUser
in MongoDB, which is owned by theresource server.Then the callback controller should store user info into thatUser
collection.Also the addedUser
collection will not contain password or other credential info of account.
As anauthorization server, the Node.js server owns user accounts and profiles.It can respond authorization requests coming fromresource server, i.e. verify the access token, then send back an correspondingUser
object to theresource server if the token is valid.
Folder Path | Description |
---|---|
server/config/passport-provider.ts | We name it suffixed with provider because it's the access token's provider. In this file theauthorization server handles sign-in requests byLocalStrategy , and handles authorization requests byBearerStrategy . |
server/config/oauth2orize-server.ts | oauth2orize is an authorization server toolkit for Node.js. In this file we imported oauth2orize and defined how should the OAuth2Server work in each of the authorization phases. |
server/routes/oauth2.ts | Defines routes start with/oauth2 , these routes configuresauthorization server on how to handle authorize, login, sign up, and profile updating, etc. |
server/controller/oauth2.ts | Defines route handlers on how to handle authorize, login, sign up, and profile updating, etc. It works with the OAuth2Server defined inserver/config/oauth2orize-server.ts, and executes DB operations inUser collection. |
For developers who would like to use the authorizing mechanism in this project, he/she just need to know how to protect his/her REST API.This authorizing mechanism isthe second key point to make the RESTful architecture possible.
For developers who would like to go deeper, we can summarize for them that:
- The Node.js server in this project not only take the role ofresource server, but also take the role ofauthorization server.
- All requests toresource server are routed to path start with
/auth
or/api
; all requests toauthorization server are routed to path start with/oauth2
. - If one would like to import 3rd party token provider orauthorization server, he/she should make changes in theresource server part.
- You can improve the OAuth2authorization server implementation by addingexpiration time orscope in the
AccessToken
collection. Also necessary code changes should be made inauthorization server part.
In sectionCreate React App we have mentioned how the Node.js server serves React app and REST APIs.Briefly speaking, the Node.js server complete this task in a very simple way.
- If the url of request starts with
/api
,/auth
, or/oauth2
, handle it using its own route handlers. - For all other request url, return theclient/core/build/static/index.html.
The client powered byreact-router 4.0+ handles all routes except those were handled by Node.js server.In the fileclient/core/src/App.tsx, react-router 4.0+ shows how this works can be done elegantly.
exportdefaultclassAppextendsReact.Component<Props,States>{render():React.ReactElement<any>{constnotFoundError:Error={name:"404 Not Found",message:`not found for${window.location.href} `};return(<div><Routerender={(props)=><Header{...props}/>}/><Switch><Routeexactpath="/"render={(props)=><Home{...props}/>}/><Routepath="/login"render={(props)=><LogIn{...props}/>}/><Routepath="/signup"render={(props)=><SignUp{...props}/>}/><Routepath="/consent"render={(props)=><Consent{...props}/>}/><Routepath="/profile"render={(props)=><Profile{...props}/>}/>{/* add more routes here */}<Routerender={(props)=><ErrorPage{...props}error={notFoundError}/>}/></Switch><Footer/></div>);}}
<Route>
s in the<Switch>
can only be rendered the first matched one.If none of the<Route>
s matched,<ErrorPage error={notFoundError}>
will be rendered.Please refer to thedocument for more usages of react-router 4.0+.
This RESTful architecture brings significant performance improvement from several aspects:
- The Node.js server reduces its response payload.
- The Node.js server gets rid of the work on constructing html pages.
- Since the fileclient/core/build/static/index.html would not change unless you re-deploy you app, it's easily be cached among Internet.
- The React app handles navigation locally, users feel no latency when they navigate back and forth in the browser.
In this project, there is no separated file likeclient/core/public/css/main.css.We are usingSemantic-UI React because it has following advantages.
- React style. It's components match the mental model React has given us for composing UI great. Most of the time you are shaping your UI using props instead of classes or inline styles.
- It provides many useful components which are suitable to compose a blog app.Feed,Rating, andComment are fancy examples.
- Good support for react-router.
- As its name hinted, using Semantic-UI React you can build your virtual DOM easy-to-read and compact.
On the other hand, you can easily change the styles of React app by importother 3rd party libraries and replace existing raw components.Please refer toofficial document for more solutions on this problem.
For the ReactNative project, we are usingNativeBase as a UI library, which takes similar role as Semantic-UI for React project.
In this project, you can debug your server and clientat the same time.You can debug the server in VS Code, meanwhile you can debug the client inChrome.
Note! If you are going to debug your app in local area networks (LAN),please change the
HOST_NAME_DEV
in fileclient/core/src/models/HostUrl.ts fromlocalhost
to your host IP,e.g.192.168.1.13
.Debugging your app on mobile devices usually requires debugging in LAN.
We useYarn as the package manager of this project.You can usenpm
if you prefer.You can run following commands usingyarn <command>
.
Yarn Script | Description |
---|---|
install | Install all dependencies defined inpackage.json andclient/core/package.json . |
install-server | Install all dependencies defined inpackage.json . |
install-client | Install all dependencies defined inclient/core/package.json . |
postinstall | Runs automatically afteryarn install complete its own task.Do not run it manually. |
build-server | Full build on server. Runs ALL build tasks (build-ts ,tslint ). |
build | Full build on both server and client |
debug | Performs a full build and then serves the app in watch mode. You can debug both client and server in this circumstance. |
start-client | Build web client indevelopment environment and start the debugger of client |
start-mobile | Build ReactNative project and start the Expo debugging tool, in the Expo debugging tool page you can start mobile client easily |
start | Start node server and start the debugger of client. In this circumstance you can only debug the client. |
serve | Runs node ondist/server.js which is the apps entry point. |
build-ts | Compiles all source.ts files to.js files in thedist folder. |
watch-ts | Same asbuild-ts but continuously watches.ts files and re-compiles when needed. |
tslint | Runs TSLint on project files |
test | Not ready yet. |
serve-debug | Runs the app with the --inspect flag. |
watch-debug | The same aswatch but includes the --inspect flag so you can attach a debugger. |
After you run the commandyarn start
oryarn debug
, you can visithttp://localhost:3000 using Chrome.HitF12
to open the debug mode of Chrome.You can see the debugging window shows on the right side or bottom of the browser.There are 2 important tabs in the debugging window.
- Console. You can see logs from client in this tab. We added the
redux-logger
middleware in client so you can inspect the redux states transition in this tab. - Source. You can find your source code files in this tab. Usually your source code folder shows an orange icon. Also you can set break point and watch variables in this tab.
From theofficial document you may find the best practice on how to debug using Chrome.
You can also integrateReact DevTools to boost your debug experience.
After you run the commandyarn start
oryarn debug
, you can visithttp://localhost:19002 using Chrome. This is the home page of Expo debugging tool.
For detail of how to debuggingplease refer toExpo debugging document.Briefly speaking you will have similar experience as debugging on website client, they are both using Chrome.
[TODO] This part will be elaborated.
Debugging is one of the places where VS Code really shines over other editors. Node.js debugging in VS Code is easy to setup and even easier to use.This project comes pre-configured with everything you need to get started.
To start debugging server you need to run the command first.
yarn debug
Then hitF5
in VS Code, it looks for a top level.vscode
folder with alaunch.json
file.In this file, you can tell VS Code exactly what you want to do:
{"type":"node","request":"attach","name":"Attach by Process ID","processId":"${command:PickProcess}","protocol":"inspector"}
This is mostly identical to the "Node.js: Attach by Process ID" template with one minor change.We added"protocol": "inspector"
which tells VS Code that we're using the latest version of Node which uses a new debug protocol.
With this file in place, you can hitF5
to attach a debugger.You will probably have multiple node processes running, so you need to find the one that showsnode dist/server/server.js
.Now you can set your breakpoints and wait for them be hit.
Please refer toTests section.
You can run all of the tests in project usingyarn test
, or run tests for client only usingyarn test-client
.
Also you can easily debug all of the client tests in debug window of VSCode, since we have configured the debug process in.vscode/launch.json.
We will add more tests and grow the code coverage in near future.
There are many ways to deploy an Node app, and in general, nothing about the deployment process changes because you're using TypeScript.In this section, let's show you how to deploy this project to Azure App Service.
- Azure account - If you don't have one, you can sign up for free.The Azure free tier gives you plenty of resources to play around with including up to 10 App Service instances, which is what we will be using.
- Docker Desktop - Usually you need to sign in or sign up onDocker hub first.
In this step, you create a MongoDB database in Azure. When your app is deployed to Azure, it uses this cloud database.For MongoDB, we use Azure Cosmos DB. Cosmos DB supports MongoDB client connections.
- Sign inAzure Portal using your account.
- OpenAzure cloud shell button on the menu in the upper-right corner of the portal.
- Then if you are prompted thatYou have no storage mounted, just clickCreate Storage.
In the opened shell enter following command.Change the location according to your preference.
az group create --name myResourceGroup --location"West Europe"
After entering this you should get a Json response in the shell.With following property in it.
"properties": {"provisioningState":"Succeeded"}
In the following command, substitute a unique Cosmos DB name for the<cosmosdb_name>
placeholder.This name is used as the part of the Cosmos DB endpoint,https://<cosmosdb_name>.documents.azure.com/, so the name needs to be unique across all Cosmos DB accounts in Azure.The name must contain only lowercase letters, numbers, and the hyphen (-) character, and must be between 3 and 50 characters long.
az cosmosdb create --name<cosmosdb_name> --resource-group myResourceGroup --kind MongoDB
When the Cosmos DB account is created several minutes later, the Azure CLI shows information similar to the following example:
{"consistencyPolicy": {"defaultConsistencyLevel":"Session","maxIntervalInSeconds":5,"maxStalenessPrefix":100 },"databaseAccountOfferType":"Standard","documentEndpoint":"https://<cosmosdb_name>.documents.azure.com:443/","failoverPolicies":"","..." :"Output truncated for readability"}
To connect to the Cosmos DB database, you need the database key.In the Cloud Shell, use following command to retrieve the primary key.
az cosmosdb list-keys --name<cosmosdb_name> --resource-group myResourceGroup
The Azure CLI shows information similar to the following example:
{"primaryMasterKey":"RS4CmUwzGRASJPMoc0kiEvdnKmxyRILC9BWisAYh3Hq4zBYKr0XQiSE4pqx3UchBeO4QRCzUt1i7w0rOkitoJw==","primaryReadonlyMasterKey":"HvitsjIYz8TwRmIuPEUAALRwqgKOzJUjW22wPL2U8zoMVhGvregBkBk9LdMTxqBgDETSq7obbwZtdeFY7hElTg==","secondaryMasterKey":"Lu9aeZTiXU4PjuuyGBbvS1N9IRG3oegIrIh95U6VOstf9bJiiIpw3IfwSUgQWSEYM3VeEyrhHJ4rn3Ci0vuFqA==","secondaryReadonlyMasterKey":"LpsCicpVZqHRy7qbMgrzbRKjbYCwCKPQRl0QpgReAOxMcggTvxJFA94fTi0oQ7xtxpftTJcXkjTirQ0pT7QFrQ=="}
Copy the value ofprimaryMasterKey
. You need this information in the next step.
In your Typescript MERN project open file.env.production.Replace the two<cosmosdb_name>
placeholders with your Cosmos DB database name, and replace the<primary_master_key>
placeholder with the key you copied in the previous step.
MONGODB_URI=mongodb://<cosmosdb_name>:<primary_master_key>@<cosmosdb_name>.documents.azure.com:10250/typescript-mern-starter?ssl=true&sslverifycertificate=false
Thessl=true
option is required because Cosmos DB requires SSL. Save your changes.
MongoDB Atlas provides free tier cluster for up to 512MB storage. It is more suitable for startup or prototype projects comparing to Azure CosmosDB.
To build a free MongoDB service, you can follow below steps.
- Register a MongoDB account.
- After sign in to the cloud atlas, create a free tier (M0 Sandbox) cluster which take Azure as cloud provider.
- ClickConnect button in the cluster you just created.
- In step(1) Whitelist your connection IP address, remove the IP restriction by whitelist all IPs (filling 0.0.0.0/0).
- In step(2) Create a MongoDB User, create a cluster user with admin privilege. Using auto generated password and copy it to somewhere so that you can paste it back later.
- ClickChoose a connection method.
- ClickConnect your application.
- In step(1) Choose your driver version, select driver asNode.js and version as2.2.12 or later.
- Click to copy the connection string.
In your Typescript MERN project open file.env.production.Modify the variableMONGODB_URI
using the connection string you just copied.
mongodb://<cluster_user_name>:<password>@cluster0-shard-00-00-pyou0.azure.mongodb.net:27017,cluster0-shard-00-01-pyou0.azure.mongodb.net:27017,cluster0-shard-00-02-pyou0.azure.mongodb.net:27017/<your_db_name>?ssl=true&replicaSet=Cluster0-shard-0&authSource=admin&retryWrites=true&w=majority
Remember to replace the placeholders in above connection string example, especially<your_db_name>
.
Blob storage is used to store multi-medias.Now we support Azure blob storage in this project.You can switch to other cloud storage providers easily by modifying the code inserver/repository/.
Please refer tothis document to create a new storage account on Azure.
Copy the account name and access key in Settings.Then you can paste yourSTORAGE_ACCOUNT
andSTORAGE_ACCOUNT_KEY
in the file.env.production.
Note! In development environment, the project store the files in disk instead of cloud storage.Before you can test the cloud storage in development environment,you need to modifyserver\repository\storage.ts,and paste your
STORAGE_ACCOUNT
andSTORAGE_ACCOUNT_KEY
in the file.env.development.
In fileclient/core/src/models/HostUrl.ts, change theHOST_NAME_PROD
to thehttp://localhost
.Meanwhile change theHOST_PORT_PROD
from 80 to 3000 because your local 80 port may have been blocked.
Open your local terminal.Switch the local node env fromdevelopment
toproduction
by entering following commands.
## bashexport NODE_ENV=production
## powershell$env:NODE_ENV="production"
Please remember to switch theNODE_ENV
back todevelopment
once you are done this section.
Build the production for your TypeScript MERN project.
yarn build
Start your local production server.
yarn serve
Navigate tohttp://localhost:3000 in a browser.ClickSign Up in the top menu and create a test user.If you are successful creating a user and logging in, then your app is writing data to the Cosmos DB database in Azure successfully.You can verify the data in your Azure portal'sAzure Cosmos DB account page usingData Explorer or in yourMongoDB Cluster by clicking collection button.
In the terminal, stop Node.js by typingCtrl+C.
Firstly, we willdeploy the production app to Docker image because of its great flexibility.Nowadays almost every cloud service support deploying web app using Docker image.Therefore once we have a well constructed Docker image then we can easily deploy our app toany platform with little adaption.In next section we will deploy the Docker image generated in this section toAzure app service.Let's generate the Docker image locally in this section.
In fileclient/core/src/models/HostUrl.ts, change theHOST_NAME_PROD
to the target app url.Meanwhile change theHOST_PORT_PROD
from 3000 back to 80.
At the same time, in the string arrayCORS_WHITELIST
, you need to addall of your possible domain names into it.For example,"https://www.tsmernstarter.com"
and"https://tsmernstarter.com"
.
We are ready to build the app.Make sure you are still at the root directory in your repository, which includes aDockerfile
Now run the build command.This creates a Docker image, which we’re going to name using the --tag option.Use -t if you want to use the shorter option.Replace <your_app_name> with your desired app name.This command will take your several minutes to finish for the first time you run it.
docker build --tag=<your_app_name>.
Where is your built image?It’s in your machine’s local Docker image registry:
$ docker image lsREPOSITORY TAG IMAGE ID<your_app_name> latest 326387cea398
If you don’t have a Docker account, sign up for one athub.docker.com.Make note of <your_username>.
Log in to the Docker public registry on your local machine.
docker login
We need to tag the image with a meaningful version label using following command, e.g. azure.Replace <your_app_name> and <your_username>, and run the command.
docker tag<your_app_name><your_username>/<your_app_name>:azure
Finally, upload your tagged image to the repository:
docker push<your_username>/<your_app_name>:azure
Once complete, the results of this upload are publicly available.If you log in to Docker Hub, you see the new image there, with its pull command.
Traditionally, we can deploy a node.js app using git repository on Azure app service.That is to say, you can push your git repository to the Azure app service, then it will start your app by runningnpm run start
.In practice this is not a easy-to-use approach since you have very limited ability to control the installation and build process for your app on Azure app service.
Using Docker image you can get rid of many annoying environment issues, and will easily migrate your app to other cloud platforms in future.
Open Azure portal, click the quick link ofApp Services.Then click the+Add button.You will probably see the portal looks like following:
Fill the create wizard like this.
- Resource Group: clickCreate new, and name it like
myLinuxGroup
- Name: use <your_app_name>
- Publish: clickDocker Image
- Operating System: Linux
- Region: choose one near your place
- Sku and size: Free F1 is OK, you can scale it up later on your demandThen clickNext: Docker >, you will probably see the portal looks like following:
Fill this wizard like following.
- Options: keep it asSingle Container
- Image Source:Docker Hub
- Access Type: keep itpublic
- Image and tag: <your_username>/<your_app_name>:azure, the one you just published in previous section
Finally you are ready to create your app service with Docker image.ClickReview and Create.Then clickCreate after the portal navigate you to the next page.
After clickcreate for the Azure app service, you will get the finish notification.
Note! Indeed it is still preparing the resources internally.If you see 5xx error on your site athttps://<your_app_name>.azurewebsites.net, don't feel frustrated.You have to wait up to 20 minutes until you can visit your new app successfully.
After the app is ready, please note that the browser has marked your site trustful, i.e. Azure has served SSL for your site.
Congratulation! You have deployed a real working web app!
Usually the web addresshttps://<your_app_name>.azurewebsites.net cannot fulfil your requirement for a real product, since you would like to give your customer a more concise and noticeable url.You can follow the steps below to adapt the app you just deployed on Azure to a desired domain name.
- Register a domain you like.
- Modify yourclient/core/src/models/HostUrl.ts, so that
export const HOST_NAME_PROD: string = "https://<domain_name_you_registered>";
- Re-deploy your app tohttps://<your_app_name>.azurewebsites.net by following steps introduced in previous sections.
- Follow the tutorial ofmap custom domain.
- Follow the tutorial ofbind SSL certificate. Please note that bind a valid SSL certificate is must-do for Typescript-MERN-starter projects.
Please refer toExpo publishing documents for detail steps.Expo managed the deployment process sophisticatedly for you.
[TODO] This part will be elaborated.
From this part we can see that deploy a Typescript-MERN-starter project to Azure is very easy.What you have to be worry about is just to configure the environment variables correctly.
Further more, you can do following to improve your engineering experience if you are using Azure as your cloud platform.
- You canconfigure the CI/CD procedure on Azure app service so that you can save a lot of mouse click in future.
- You can write your owndocker-compose.yml to manage/orchestrate multiple services including your app service. For example you can add themongodb service so that you can avoid using the Azure Cosmos DB. Then when you deploy your docker image, chooseDocker Compose (Preview) instead ofSingle Container forOptions.
If you don't like Azure, e.g. you prefer AWS, you can easilydeploy your Docker image to AWS too.The flexibility of Docker make the migration from one platform to another very simple.
About
Build a real fullstack app (backend+website+mobile) in 100% Typescript
Topics
Resources
License
Code of conduct
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.