Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Build a real fullstack app (backend+website+mobile) in 100% Typescript

License

NotificationsYou must be signed in to change notification settings

shanhuiyang/TypeScript-MERN-Starter

Repository files navigation

Build StatusDependency StatusPRs welcome

Live Demo:https://hd283.net/

Supported ClientsPreview
WebWeb Preview
Web (Mobile)Mobile Web PreviewMobile Web PreviewMobile Web Preview
AndroidAndroid PreviewAndroid PreviewAndroid Preview
iOSiOS PreviewiOS PreviewiOS Preview

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 modeledUser 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.

Table of Content

Quick start

Before Start

To build and run this app locally you will need a few things:

Clone the repository

git clone https://github.com/shanhuiyang/TypeScript-MERN-Starter.git<project_name>

Install dependencies

cd<project_name>yarn

Start your mongoDB server

(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.

Build and run the project

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.

Start the mobile apps

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 accesshttp://localhost which hosted on the same server/PC/Mac.You can test your app in the same LAN, see next section for detail.

Optional: Test your app on other device in the same LAN

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.

  1. Get the IP address (either global IP or LAN IP) of your development server.
  2. Modify the constantHOST_NAME_DEV inclient/core/src/models/HostUrl.ts with the IP address you got.

Optional: Test with OTP sending

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:

  1. add your email account and password toserver/config/smtp-transporter.ts.
  2. change the flag totrue inclient/core/src/shared/constants.ts.

Motivation

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.

Prior Art

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.

Philosophy

We build this project on following philosophies:

  1. Beginner friendly. You can start the app without any complex configuration for build and runtime.
  2. 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.
  3. 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.

Work items

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.

Project structure

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.

Big picture

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.

Data flow

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.

Dependencies

Dependencies

Here is a index fordocuments what the project depends on:

Folder structure

.                                // 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

Create React App

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:

  1. 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.
  2. 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.

Serve static assets of client and index.html in production

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.

Proxy API requests in development

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:

  1. Assign port 3000 to the serviceWorker, and assign port 3001 to the Node.js server.
  2. When we are runningyarn start in project root path, these 2 servers will be started concurrently.
  3. All client requests are going to the port 3000, i.e. the serviceWorker.
  4. 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.

Unified Modeling

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:

  1. Add a interface namedUser inclient/core/src/models/User.ts, and extends it fromUnifiedModel.
  2. Fill theUser with fields you desire such as name and address, etc.
  3. Inserver/models/User/UserDocument.d.ts, define a interface namedUserDocument, which extendsUser andmongoose.Document simultaneously. This interface represent a document which can be create, read, update and delete from MongoDB.
  4. Add theuserSchema 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.

Embedded OAuth2 server

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.

  1. Authorize the request with Bearer strategy, just see if it has a valid Bearer token. If yes, go to step 2; if no, return a401 Unauthorized error.
  2. 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 protocol

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:

  1. The application does not need to store the user's username and password.
  2. 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:

  1. 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.
  2. resource server.The server hosting the protected resources, capable of accepting and responding to protected resource requests using access tokens.
  3. 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.
  4. 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.

Resource 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 PathDescription
server/config/passport-consumer.tsWe 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.tsIn 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.tsDefines 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.tsCRUD operations of article. In this file theresource server does its own jobs.
server/controller/auth.tsDefines 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 namedUser 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.

Authorization server

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 PathDescription
server/config/passport-provider.tsWe 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.tsoauth2orize 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.tsDefines routes start with/oauth2, these routes configuresauthorization server on how to handle authorize, login, sign up, and profile updating, etc.
server/controller/oauth2.tsDefines 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.

Summary of OAuth2 server

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:

  1. The Node.js server in this project not only take the role ofresource server, but also take the role ofauthorization server.
  2. All requests toresource server are routed to path start with/auth or/api; all requests toauthorization server are routed to path start with/oauth2.
  3. If one would like to import 3rd party token provider orauthorization server, he/she should make changes in theresource server part.
  4. You can improve the OAuth2authorization server implementation by addingexpiration time orscope in theAccessToken collection. Also necessary code changes should be made inauthorization server part.

Server routing & client routing

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.

  1. If the url of request starts with/api,/auth, or/oauth2, handle it using its own route handlers.
  2. For all other request url, return theclient/core/build/static/index.html.

React-router 4.0+

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+.

Performance perspective

This RESTful architecture brings significant performance improvement from several aspects:

  1. The Node.js server reduces its response payload.
  2. The Node.js server gets rid of the work on constructing html pages.
  3. Since the fileclient/core/build/static/index.html would not change unless you re-deploy you app, it's easily be cached among Internet.
  4. The React app handles navigation locally, users feel no latency when they navigate back and forth in the browser.

Styling

In this project, there is no separated file likeclient/core/public/css/main.css.We are usingSemantic-UI React because it has following advantages.

  1. 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.
  2. It provides many useful components which are suitable to compose a blog app.Feed,Rating, andComment are fancy examples.
  3. Good support for react-router.
  4. 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.

How to debug

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 theHOST_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.

Available scripts

We useYarn as the package manager of this project.You can usenpm if you prefer.You can run following commands usingyarn <command>.

Yarn ScriptDescription
installInstall all dependencies defined inpackage.json andclient/core/package.json.
install-serverInstall all dependencies defined inpackage.json.
install-clientInstall all dependencies defined inclient/core/package.json.
postinstallRuns automatically afteryarn install complete its own task.Do not run it manually.
build-serverFull build on server. Runs ALL build tasks (build-ts,tslint).
buildFull build on both server and client
debugPerforms a full build and then serves the app in watch mode. You can debug both client and server in this circumstance.
start-clientBuild web client indevelopment environment and start the debugger of client
start-mobileBuild ReactNative project and start the Expo debugging tool, in the Expo debugging tool page you can start mobile client easily
startStart node server and start the debugger of client. In this circumstance you can only debug the client.
serveRuns node ondist/server.js which is the apps entry point.
build-tsCompiles all source.ts files to.js files in thedist folder.
watch-tsSame asbuild-ts but continuously watches.ts files and re-compiles when needed.
tslintRuns TSLint on project files
testNot ready yet.
serve-debugRuns the app with the --inspect flag.
watch-debugThe same aswatch but includes the --inspect flag so you can attach a debugger.

Debugging website client

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.

  1. Console. You can see logs from client in this tab. We added theredux-logger middleware in client so you can inspect the redux states transition in this tab.
  2. 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.

Debugging mobile client

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 server

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.

Debugging tests

Please refer toTests section.

Tests

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.

Deploying the app

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.

Prerequisite

  • 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.

Create production MongoDB (Option 1: Azure CosmosDB)

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.

Open Azure Cloud Shell

  1. Sign inAzure Portal using your account.
  2. OpenAzure cloud shell button on the menu in the upper-right corner of the portal.
  3. Then if you are prompted thatYou have no storage mounted, just clickCreate Storage.

Create a resource group

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"}

Create a Cosmos DB account

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"}

Retrieve the database key

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.

Configure the connection string in your Node.js application

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.

Create production MongoDB (Option 2: MongoDB Atlas)

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.

  1. Register a MongoDB account.
  2. After sign in to the cloud atlas, create a free tier (M0 Sandbox) cluster which take Azure as cloud provider.
  3. ClickConnect button in the cluster you just created.
  4. In step(1) Whitelist your connection IP address, remove the IP restriction by whitelist all IPs (filling 0.0.0.0/0).
  5. 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.
  6. ClickChoose a connection method.
  7. ClickConnect your application.
  8. In step(1) Choose your driver version, select driver asNode.js and version as2.2.12 or later.
  9. 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>.

Create a production blob storage

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 yourSTORAGE_ACCOUNT andSTORAGE_ACCOUNT_KEY in the file.env.development.

Test the application in production mode

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.

Build app into a Docker image

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.

Configure environment variables for production

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".

Build app into a local Docker image

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

Push Docker image to Docker hub

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.

Create an Azure app service using Docker image

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.

Add an Azure app service in Azure portal

Open Azure portal, click the quick link ofApp Services.Then click the+Add button.You will probably see the portal looks like following:

Create an Azure app service in Azure portal

Fill the create wizard like this.

  • Resource Group: clickCreate new, and name it likemyLinuxGroup
  • 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:

Choose a Docker image for your app service

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.

Verify your Azure app service

After clickcreate for the Azure app service, you will get the finish notification.

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!

Map custom domain and bind SSL certificate

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.

  1. Register a domain you like.
  2. Modify yourclient/core/src/models/HostUrl.ts, so thatexport const HOST_NAME_PROD: string = "https://<domain_name_you_registered>";
  3. Re-deploy your app tohttps://<your_app_name>.azurewebsites.net by following steps introduced in previous sections.
  4. Follow the tutorial ofmap custom domain.
  5. Follow the tutorial ofbind SSL certificate. Please note that bind a valid SSL certificate is must-do for Typescript-MERN-starter projects.

Deploy the mobile apps

Please refer toExpo publishing documents for detail steps.Expo managed the deployment process sophisticatedly for you.

[TODO] This part will be elaborated.

Summary of deployment

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.

  1. You canconfigure the CI/CD procedure on Azure app service so that you can save a lot of mouse click in future.
  2. 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.

Releases

No releases published

Packages

No packages published

Contributors31


[8]ページ先頭

©2009-2025 Movatter.jp