├── .babelrc
├── .browserslistrc
├── .dockerignore
├── .editorconfig
├── .env.example
├── .gitignore
├── .nvmrc
├── .vscode
└── launch.json
├── Dockerfile
├── README.md
├── ant-theme-vars.less
├── assets
├── favicon.ico
└── images
│ ├── flow.png
│ └── logo.png
├── client
├── About
│ └── About.tsx
├── App.less
├── App.tsx
├── Home
│ ├── Home.less
│ ├── Home.tsx
│ └── NotifDemo.tsx
├── Layout
│ ├── AppHeader.less
│ ├── AppHeader.tsx
│ ├── AppLayout.less
│ ├── AppLayout.tsx
│ ├── AppSider.less
│ ├── AppSider.tsx
│ └── Routing
│ │ ├── AppBreadcrumbs.tsx
│ │ ├── AppRouter.tsx
│ │ └── RouteConfigs.ts
├── User
│ └── UserList.tsx
├── client.tsx
├── setupEnzyme.ts
├── tsconfig.client.json
└── utils
│ └── .gitkeep
├── docker-compose.build.yml
├── docker-compose.yml
├── index.js
├── nest-cli.json
├── package-lock.json
├── package.json
├── server
├── config.ts
├── src
│ ├── api
│ │ ├── api.module.ts
│ │ ├── global
│ │ │ ├── auth
│ │ │ │ ├── auth.guard.ts
│ │ │ │ └── auth.module.ts
│ │ │ ├── config
│ │ │ │ ├── config.service.spec.ts
│ │ │ │ ├── config.service.ts
│ │ │ │ └── db-config
│ │ │ │ │ ├── db-config.service.spec.ts
│ │ │ │ │ └── db-config.service.ts
│ │ │ ├── global.module.ts
│ │ │ └── index.ts
│ │ └── user
│ │ │ ├── controllers
│ │ │ ├── v1.user.controller.spec.ts
│ │ │ └── v1.user.controller.ts
│ │ │ ├── services
│ │ │ ├── user.service.spec.ts
│ │ │ └── user.service.ts
│ │ │ └── user.module.ts
│ ├── app.module.ts
│ ├── main.ts
│ ├── spa
│ │ ├── app.controller.spec.ts
│ │ ├── manifest-manager
│ │ │ ├── manifest-manager.service.spec.ts
│ │ │ └── manifest-manager.service.ts
│ │ ├── spa.controller.ts
│ │ └── spaModule.ts
│ └── statics-router.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.json
└── tsconfig.test.json
├── shared
├── .gitkeep
└── entities
│ ├── entityBase.ts
│ ├── partials
│ └── name.ts
│ └── user
│ └── user.entity.ts
├── tsconfig.json
├── tsconfig.webpack-config.json
├── tslint.json
├── types.d.ts
├── views
└── page.ejs
└── webpack.config.ts
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/env",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "@babel/react",
10 | "@babel/typescript"
11 | ],
12 | "plugins": [
13 | "react-hot-loader/babel",
14 | "babel-plugin-transform-typescript-metadata",
15 | ["@babel/plugin-proposal-decorators", { "legacy": true }],
16 | ["@babel/plugin-proposal-class-properties", { "loose": true }],
17 | "@babel/proposal-numeric-separator",
18 | "@babel/transform-runtime",
19 | "@babel/proposal-object-rest-spread",
20 | ["import", { "libraryName": "antd", "style": true }]
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | last 2 version
2 | > 2%
3 | maintained node versions
4 | not dead
5 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | Dockerfile*
4 | docker-compose*
5 | .dockerignore
6 | .git
7 | .gitignore
8 | README.md
9 | .idea
10 | .env*
11 | .nvmrc
12 | .babelrc
13 | .browserslistrc
14 | webpack.config.ts
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | max_line_length = 120
10 | tab_width = 4
11 | ij_continuation_indent_size = 8
12 | ij_formatter_off_tag = @formatter:off
13 | ij_formatter_on_tag = @formatter:on
14 | ij_formatter_tags_enabled = false
15 | ij_smart_tabs = false
16 | ij_wrap_on_typing = false
17 |
18 | [*.css]
19 | ij_css_align_closing_brace_with_properties = false
20 | ij_css_blank_lines_around_nested_selector = 1
21 | ij_css_blank_lines_between_blocks = 1
22 | ij_css_brace_placement = 0
23 | ij_css_hex_color_long_format = false
24 | ij_css_hex_color_lower_case = false
25 | ij_css_hex_color_short_format = false
26 | ij_css_hex_color_upper_case = false
27 | ij_css_keep_blank_lines_in_code = 2
28 | ij_css_keep_indents_on_empty_lines = false
29 | ij_css_keep_single_line_blocks = false
30 | ij_css_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
31 | ij_css_space_after_colon = true
32 | ij_css_space_before_opening_brace = true
33 | ij_css_value_alignment = 0
34 |
35 | [*.less]
36 | indent_size = 2
37 | ij_less_align_closing_brace_with_properties = false
38 | ij_less_blank_lines_around_nested_selector = 1
39 | ij_less_blank_lines_between_blocks = 1
40 | ij_less_brace_placement = 0
41 | ij_less_hex_color_long_format = false
42 | ij_less_hex_color_lower_case = false
43 | ij_less_hex_color_short_format = false
44 | ij_less_hex_color_upper_case = false
45 | ij_less_keep_blank_lines_in_code = 2
46 | ij_less_keep_indents_on_empty_lines = false
47 | ij_less_keep_single_line_blocks = false
48 | ij_less_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
49 | ij_less_space_after_colon = true
50 | ij_less_space_before_opening_brace = true
51 | ij_less_value_alignment = 0
52 |
53 | [.editorconfig]
54 | ij_editorconfig_align_group_field_declarations = false
55 | ij_editorconfig_space_after_colon = false
56 | ij_editorconfig_space_after_comma = true
57 | ij_editorconfig_space_before_colon = false
58 | ij_editorconfig_space_before_comma = false
59 | ij_editorconfig_spaces_around_assignment_operators = true
60 |
61 | [{*.ats, *.ts}]
62 | ij_continuation_indent_size = 4
63 | ij_typescript_align_imports = false
64 | ij_typescript_align_multiline_array_initializer_expression = false
65 | ij_typescript_align_multiline_binary_operation = false
66 | ij_typescript_align_multiline_chained_methods = false
67 | ij_typescript_align_multiline_extends_list = false
68 | ij_typescript_align_multiline_for = true
69 | ij_typescript_align_multiline_parameters = true
70 | ij_typescript_align_multiline_parameters_in_calls = false
71 | ij_typescript_align_multiline_ternary_operation = false
72 | ij_typescript_align_object_properties = 0
73 | ij_typescript_align_union_types = false
74 | ij_typescript_align_var_statements = 0
75 | ij_typescript_array_initializer_new_line_after_left_brace = false
76 | ij_typescript_array_initializer_right_brace_on_new_line = false
77 | ij_typescript_array_initializer_wrap = off
78 | ij_typescript_assignment_wrap = off
79 | ij_typescript_binary_operation_sign_on_next_line = false
80 | ij_typescript_binary_operation_wrap = off
81 | ij_typescript_blacklist_imports = rxjs/Rx, node_modules/**/*, @angular/material, @angular/material/typings/**
82 | ij_typescript_blank_lines_after_imports = 1
83 | ij_typescript_blank_lines_around_class = 1
84 | ij_typescript_blank_lines_around_field = 0
85 | ij_typescript_blank_lines_around_field_in_interface = 0
86 | ij_typescript_blank_lines_around_function = 1
87 | ij_typescript_blank_lines_around_method = 1
88 | ij_typescript_blank_lines_around_method_in_interface = 1
89 | ij_typescript_block_brace_style = end_of_line
90 | ij_typescript_call_parameters_new_line_after_left_paren = false
91 | ij_typescript_call_parameters_right_paren_on_new_line = false
92 | ij_typescript_call_parameters_wrap = off
93 | ij_typescript_catch_on_new_line = false
94 | ij_typescript_chained_call_dot_on_new_line = true
95 | ij_typescript_class_brace_style = end_of_line
96 | ij_typescript_comma_on_new_line = false
97 | ij_typescript_do_while_brace_force = never
98 | ij_typescript_else_on_new_line = false
99 | ij_typescript_enforce_trailing_comma = keep
100 | ij_typescript_extends_keyword_wrap = off
101 | ij_typescript_extends_list_wrap = off
102 | ij_typescript_field_prefix = _
103 | ij_typescript_file_name_style = relaxed
104 | ij_typescript_finally_on_new_line = false
105 | ij_typescript_for_brace_force = never
106 | ij_typescript_for_statement_new_line_after_left_paren = false
107 | ij_typescript_for_statement_right_paren_on_new_line = false
108 | ij_typescript_for_statement_wrap = off
109 | ij_typescript_force_quote_style = false
110 | ij_typescript_force_semicolon_style = false
111 | ij_typescript_function_expression_brace_style = end_of_line
112 | ij_typescript_if_brace_force = never
113 | ij_typescript_import_merge_members = global
114 | ij_typescript_import_prefer_absolute_path = global
115 | ij_typescript_import_sort_members = true
116 | ij_typescript_import_sort_module_name = true
117 | ij_typescript_import_use_node_resolution = true
118 | ij_typescript_imports_wrap = on_every_item
119 | ij_typescript_indent_case_from_switch = true
120 | ij_typescript_indent_chained_calls = true
121 | ij_typescript_indent_package_children = 0
122 | ij_typescript_jsdoc_include_types = false
123 | ij_typescript_jsx_attribute_value = braces
124 | ij_typescript_keep_blank_lines_in_code = 2
125 | ij_typescript_keep_first_column_comment = true
126 | ij_typescript_keep_indents_on_empty_lines = false
127 | ij_typescript_keep_line_breaks = true
128 | ij_typescript_keep_simple_blocks_in_one_line = false
129 | ij_typescript_keep_simple_methods_in_one_line = false
130 | ij_typescript_line_comment_add_space = true
131 | ij_typescript_line_comment_at_first_column = false
132 | ij_typescript_method_brace_style = end_of_line
133 | ij_typescript_method_call_chain_wrap = off
134 | ij_typescript_method_parameters_new_line_after_left_paren = false
135 | ij_typescript_method_parameters_right_paren_on_new_line = false
136 | ij_typescript_method_parameters_wrap = off
137 | ij_typescript_object_literal_wrap = on_every_item
138 | ij_typescript_parentheses_expression_new_line_after_left_paren = false
139 | ij_typescript_parentheses_expression_right_paren_on_new_line = false
140 | ij_typescript_place_assignment_sign_on_next_line = false
141 | ij_typescript_prefer_as_type_cast = false
142 | ij_typescript_prefer_parameters_wrap = false
143 | ij_typescript_reformat_c_style_comments = false
144 | ij_typescript_space_after_colon = true
145 | ij_typescript_space_after_comma = true
146 | ij_typescript_space_after_dots_in_rest_parameter = false
147 | ij_typescript_space_after_generator_mult = true
148 | ij_typescript_space_after_property_colon = true
149 | ij_typescript_space_after_quest = true
150 | ij_typescript_space_after_type_colon = true
151 | ij_typescript_space_after_unary_not = false
152 | ij_typescript_space_before_async_arrow_lparen = true
153 | ij_typescript_space_before_catch_keyword = true
154 | ij_typescript_space_before_catch_left_brace = true
155 | ij_typescript_space_before_catch_parentheses = true
156 | ij_typescript_space_before_class_lbrace = true
157 | ij_typescript_space_before_class_left_brace = true
158 | ij_typescript_space_before_colon = true
159 | ij_typescript_space_before_comma = false
160 | ij_typescript_space_before_do_left_brace = true
161 | ij_typescript_space_before_else_keyword = true
162 | ij_typescript_space_before_else_left_brace = true
163 | ij_typescript_space_before_finally_keyword = true
164 | ij_typescript_space_before_finally_left_brace = true
165 | ij_typescript_space_before_for_left_brace = true
166 | ij_typescript_space_before_for_parentheses = true
167 | ij_typescript_space_before_for_semicolon = false
168 | ij_typescript_space_before_function_left_parenth = true
169 | ij_typescript_space_before_generator_mult = false
170 | ij_typescript_space_before_if_left_brace = true
171 | ij_typescript_space_before_if_parentheses = true
172 | ij_typescript_space_before_method_call_parentheses = false
173 | ij_typescript_space_before_method_left_brace = true
174 | ij_typescript_space_before_method_parentheses = false
175 | ij_typescript_space_before_property_colon = false
176 | ij_typescript_space_before_quest = true
177 | ij_typescript_space_before_switch_left_brace = true
178 | ij_typescript_space_before_switch_parentheses = true
179 | ij_typescript_space_before_try_left_brace = true
180 | ij_typescript_space_before_type_colon = false
181 | ij_typescript_space_before_unary_not = false
182 | ij_typescript_space_before_while_keyword = true
183 | ij_typescript_space_before_while_left_brace = true
184 | ij_typescript_space_before_while_parentheses = true
185 | ij_typescript_spaces_around_additive_operators = true
186 | ij_typescript_spaces_around_arrow_function_operator = true
187 | ij_typescript_spaces_around_assignment_operators = true
188 | ij_typescript_spaces_around_bitwise_operators = true
189 | ij_typescript_spaces_around_equality_operators = true
190 | ij_typescript_spaces_around_logical_operators = true
191 | ij_typescript_spaces_around_multiplicative_operators = true
192 | ij_typescript_spaces_around_relational_operators = true
193 | ij_typescript_spaces_around_shift_operators = true
194 | ij_typescript_spaces_around_unary_operator = false
195 | ij_typescript_spaces_within_array_initializer_brackets = false
196 | ij_typescript_spaces_within_brackets = false
197 | ij_typescript_spaces_within_catch_parentheses = false
198 | ij_typescript_spaces_within_for_parentheses = false
199 | ij_typescript_spaces_within_if_parentheses = false
200 | ij_typescript_spaces_within_imports = true
201 | ij_typescript_spaces_within_interpolation_expressions = false
202 | ij_typescript_spaces_within_method_call_parentheses = false
203 | ij_typescript_spaces_within_method_parentheses = false
204 | ij_typescript_spaces_within_object_literal_braces = true
205 | ij_typescript_spaces_within_object_type_braces = true
206 | ij_typescript_spaces_within_parentheses = false
207 | ij_typescript_spaces_within_switch_parentheses = false
208 | ij_typescript_spaces_within_type_assertion = false
209 | ij_typescript_spaces_within_union_types = true
210 | ij_typescript_spaces_within_while_parentheses = false
211 | ij_typescript_special_else_if_treatment = true
212 | ij_typescript_ternary_operation_signs_on_next_line = false
213 | ij_typescript_ternary_operation_wrap = off
214 | ij_typescript_union_types_wrap = on_every_item
215 | ij_typescript_use_chained_calls_group_indents = false
216 | ij_typescript_use_double_quotes = false
217 | ij_typescript_use_explicit_js_extension = global
218 | ij_typescript_use_path_mapping = always
219 | ij_typescript_use_public_modifier = false
220 | ij_typescript_use_semicolon_after_statement = true
221 | ij_typescript_var_declaration_wrap = normal
222 | ij_typescript_while_brace_force = never
223 | ij_typescript_while_on_new_line = false
224 | ij_typescript_wrap_comments = false
225 |
226 | [{*.cjs, *.js}]
227 | ij_continuation_indent_size = 4
228 | ij_javascript_align_imports = false
229 | ij_javascript_align_multiline_array_initializer_expression = false
230 | ij_javascript_align_multiline_binary_operation = false
231 | ij_javascript_align_multiline_chained_methods = false
232 | ij_javascript_align_multiline_extends_list = false
233 | ij_javascript_align_multiline_for = true
234 | ij_javascript_align_multiline_parameters = true
235 | ij_javascript_align_multiline_parameters_in_calls = false
236 | ij_javascript_align_multiline_ternary_operation = false
237 | ij_javascript_align_object_properties = 0
238 | ij_javascript_align_union_types = false
239 | ij_javascript_align_var_statements = 0
240 | ij_javascript_array_initializer_new_line_after_left_brace = false
241 | ij_javascript_array_initializer_right_brace_on_new_line = false
242 | ij_javascript_array_initializer_wrap = off
243 | ij_javascript_assignment_wrap = off
244 | ij_javascript_binary_operation_sign_on_next_line = false
245 | ij_javascript_binary_operation_wrap = off
246 | ij_javascript_blacklist_imports = rxjs/Rx, node_modules/**/*, @angular/material, @angular/material/typings/**
247 | ij_javascript_blank_lines_after_imports = 1
248 | ij_javascript_blank_lines_around_class = 1
249 | ij_javascript_blank_lines_around_field = 0
250 | ij_javascript_blank_lines_around_function = 1
251 | ij_javascript_blank_lines_around_method = 1
252 | ij_javascript_block_brace_style = end_of_line
253 | ij_javascript_call_parameters_new_line_after_left_paren = false
254 | ij_javascript_call_parameters_right_paren_on_new_line = false
255 | ij_javascript_call_parameters_wrap = off
256 | ij_javascript_catch_on_new_line = false
257 | ij_javascript_chained_call_dot_on_new_line = true
258 | ij_javascript_class_brace_style = end_of_line
259 | ij_javascript_comma_on_new_line = false
260 | ij_javascript_do_while_brace_force = never
261 | ij_javascript_else_on_new_line = false
262 | ij_javascript_enforce_trailing_comma = whenmultiline
263 | ij_javascript_extends_keyword_wrap = off
264 | ij_javascript_extends_list_wrap = off
265 | ij_javascript_field_prefix = _
266 | ij_javascript_file_name_style = relaxed
267 | ij_javascript_finally_on_new_line = false
268 | ij_javascript_for_brace_force = never
269 | ij_javascript_for_statement_new_line_after_left_paren = false
270 | ij_javascript_for_statement_right_paren_on_new_line = false
271 | ij_javascript_for_statement_wrap = off
272 | ij_javascript_force_quote_style = false
273 | ij_javascript_force_semicolon_style = false
274 | ij_javascript_function_expression_brace_style = end_of_line
275 | ij_javascript_if_brace_force = never
276 | ij_javascript_import_merge_members = global
277 | ij_javascript_import_prefer_absolute_path = global
278 | ij_javascript_import_sort_members = true
279 | ij_javascript_import_sort_module_name = true
280 | ij_javascript_import_use_node_resolution = true
281 | ij_javascript_imports_wrap = split_into_lines
282 | ij_javascript_indent_case_from_switch = true
283 | ij_javascript_indent_chained_calls = true
284 | ij_javascript_indent_package_children = 0
285 | ij_javascript_jsx_attribute_value = braces
286 | ij_javascript_keep_blank_lines_in_code = 2
287 | ij_javascript_keep_first_column_comment = true
288 | ij_javascript_keep_indents_on_empty_lines = false
289 | ij_javascript_keep_line_breaks = true
290 | ij_javascript_keep_simple_blocks_in_one_line = false
291 | ij_javascript_keep_simple_methods_in_one_line = true
292 | ij_javascript_line_comment_add_space = true
293 | ij_javascript_line_comment_at_first_column = false
294 | ij_javascript_method_brace_style = end_of_line
295 | ij_javascript_method_call_chain_wrap = off
296 | ij_javascript_method_parameters_new_line_after_left_paren = false
297 | ij_javascript_method_parameters_right_paren_on_new_line = false
298 | ij_javascript_method_parameters_wrap = off
299 | ij_javascript_object_literal_wrap = on_every_item
300 | ij_javascript_parentheses_expression_new_line_after_left_paren = false
301 | ij_javascript_parentheses_expression_right_paren_on_new_line = false
302 | ij_javascript_place_assignment_sign_on_next_line = false
303 | ij_javascript_prefer_as_type_cast = false
304 | ij_javascript_prefer_parameters_wrap = false
305 | ij_javascript_reformat_c_style_comments = false
306 | ij_javascript_space_after_colon = true
307 | ij_javascript_space_after_comma = true
308 | ij_javascript_space_after_dots_in_rest_parameter = false
309 | ij_javascript_space_after_generator_mult = true
310 | ij_javascript_space_after_property_colon = true
311 | ij_javascript_space_after_quest = true
312 | ij_javascript_space_after_type_colon = true
313 | ij_javascript_space_after_unary_not = false
314 | ij_javascript_space_before_async_arrow_lparen = true
315 | ij_javascript_space_before_catch_keyword = true
316 | ij_javascript_space_before_catch_left_brace = true
317 | ij_javascript_space_before_catch_parentheses = true
318 | ij_javascript_space_before_class_lbrace = true
319 | ij_javascript_space_before_class_left_brace = true
320 | ij_javascript_space_before_colon = true
321 | ij_javascript_space_before_comma = false
322 | ij_javascript_space_before_do_left_brace = true
323 | ij_javascript_space_before_else_keyword = true
324 | ij_javascript_space_before_else_left_brace = true
325 | ij_javascript_space_before_finally_keyword = true
326 | ij_javascript_space_before_finally_left_brace = true
327 | ij_javascript_space_before_for_left_brace = true
328 | ij_javascript_space_before_for_parentheses = true
329 | ij_javascript_space_before_for_semicolon = false
330 | ij_javascript_space_before_function_left_parenth = true
331 | ij_javascript_space_before_generator_mult = false
332 | ij_javascript_space_before_if_left_brace = true
333 | ij_javascript_space_before_if_parentheses = true
334 | ij_javascript_space_before_method_call_parentheses = false
335 | ij_javascript_space_before_method_left_brace = true
336 | ij_javascript_space_before_method_parentheses = false
337 | ij_javascript_space_before_property_colon = false
338 | ij_javascript_space_before_quest = true
339 | ij_javascript_space_before_switch_left_brace = true
340 | ij_javascript_space_before_switch_parentheses = true
341 | ij_javascript_space_before_try_left_brace = true
342 | ij_javascript_space_before_type_colon = false
343 | ij_javascript_space_before_unary_not = false
344 | ij_javascript_space_before_while_keyword = true
345 | ij_javascript_space_before_while_left_brace = true
346 | ij_javascript_space_before_while_parentheses = true
347 | ij_javascript_spaces_around_additive_operators = true
348 | ij_javascript_spaces_around_arrow_function_operator = true
349 | ij_javascript_spaces_around_assignment_operators = true
350 | ij_javascript_spaces_around_bitwise_operators = true
351 | ij_javascript_spaces_around_equality_operators = true
352 | ij_javascript_spaces_around_logical_operators = true
353 | ij_javascript_spaces_around_multiplicative_operators = true
354 | ij_javascript_spaces_around_relational_operators = true
355 | ij_javascript_spaces_around_shift_operators = true
356 | ij_javascript_spaces_around_unary_operator = false
357 | ij_javascript_spaces_within_array_initializer_brackets = false
358 | ij_javascript_spaces_within_brackets = false
359 | ij_javascript_spaces_within_catch_parentheses = false
360 | ij_javascript_spaces_within_for_parentheses = false
361 | ij_javascript_spaces_within_if_parentheses = false
362 | ij_javascript_spaces_within_imports = true
363 | ij_javascript_spaces_within_interpolation_expressions = false
364 | ij_javascript_spaces_within_method_call_parentheses = false
365 | ij_javascript_spaces_within_method_parentheses = false
366 | ij_javascript_spaces_within_object_literal_braces = true
367 | ij_javascript_spaces_within_object_type_braces = true
368 | ij_javascript_spaces_within_parentheses = false
369 | ij_javascript_spaces_within_switch_parentheses = false
370 | ij_javascript_spaces_within_type_assertion = false
371 | ij_javascript_spaces_within_union_types = true
372 | ij_javascript_spaces_within_while_parentheses = false
373 | ij_javascript_special_else_if_treatment = true
374 | ij_javascript_ternary_operation_signs_on_next_line = false
375 | ij_javascript_ternary_operation_wrap = off
376 | ij_javascript_union_types_wrap = on_every_item
377 | ij_javascript_use_chained_calls_group_indents = false
378 | ij_javascript_use_double_quotes = false
379 | ij_javascript_use_explicit_js_extension = global
380 | ij_javascript_use_path_mapping = always
381 | ij_javascript_use_public_modifier = false
382 | ij_javascript_use_semicolon_after_statement = true
383 | ij_javascript_var_declaration_wrap = normal
384 | ij_javascript_while_brace_force = never
385 | ij_javascript_while_on_new_line = false
386 | ij_javascript_wrap_comments = false
387 |
388 | [{*.html, *.sht, *.htm, *.shtm, *.shtml, *.ng}]
389 | ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3
390 | ij_html_align_attributes = true
391 | ij_html_align_text = false
392 | ij_html_attribute_wrap = normal
393 | ij_html_block_comment_at_first_column = true
394 | ij_html_do_not_align_children_of_min_lines = 0
395 | ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p
396 | ij_html_do_not_indent_children_of_tags = html, body, thead, tbody, tfoot
397 | ij_html_enforce_quotes = false
398 | ij_html_inline_tags = a, abbr, acronym, b, basefont, bdo, big, br, cite, cite, code, dfn, em, font, i, img, input, kbd, label, q, s, samp, select, small, span, strike, strong, sub, sup, textarea, tt, u, var
399 | ij_html_keep_blank_lines = 2
400 | ij_html_keep_indents_on_empty_lines = false
401 | ij_html_keep_line_breaks = true
402 | ij_html_keep_line_breaks_in_text = true
403 | ij_html_keep_whitespaces = false
404 | ij_html_keep_whitespaces_inside = span, pre, textarea
405 | ij_html_line_comment_at_first_column = true
406 | ij_html_new_line_after_last_attribute = never
407 | ij_html_new_line_before_first_attribute = never
408 | ij_html_quote_style = double
409 | ij_html_remove_new_line_before_tags = br
410 | ij_html_space_after_tag_name = false
411 | ij_html_space_around_equality_in_attribute = false
412 | ij_html_space_inside_empty_tag = true
413 | ij_html_text_wrap = normal
414 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_PORT=3001
2 | DB_HOST=localhost
3 | DB_PORT=54320
4 | DB_USERNAME=dev
5 | DB_PASSWORD=password
6 | DB_DATABASE=card-manager
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | .env
3 | .idea
4 | node_modules
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v10.16.0
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "chrome",
9 | "request": "launch",
10 | "name": "Attach to Chrome",
11 | "port": 9222,
12 | "urlFilter": "http://localhost:3001/*",
13 | "webRoot": "${workspaceFolder}"
14 | },
15 | {
16 | "name": "Launch localhost",
17 | "type": "chrome",
18 | "request": "launch",
19 | "url": "http://localhost:3001",
20 | "webRoot": "${workspaceFolder}/client",
21 | "sourceMapPathOverrides": {
22 | "webpack:///./client/*": "${webRoot}/*",
23 | "webpack:///client/*": "${webRoot}/*",
24 | "webpack:///*": "*",
25 | "webpack:///./~/*": "${webRoot}/node_modules/*"
26 | }
27 | },
28 | {
29 | "type": "node",
30 | "request": "launch",
31 | "name": "Webpack",
32 | "program": "${workspaceFolder}/node_modules/.bin/webpack-cli",
33 | "args": [
34 | "--config",
35 | "webpack.config.ts"
36 | ],
37 | "autoAttachChildProcesses": true,
38 | "runtimeExecutable": "~/.nvm/versions/node/v10.16.3/bin/node"
39 | }
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:10-alpine
2 |
3 | ENV NODE_ENV=production
4 |
5 | WORKDIR /usr/src/app
6 |
7 | COPY package*.json ./
8 | RUN npm install --production
9 |
10 | COPY ./dist ./dist
11 | COPY ./index.js .
12 |
13 | CMD [ "npm", "run", "prod" ]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Fullstack React/Typescript/NestJs starter, heavily based off of fullstack-typescript by gilamran .
6 |
7 | ---
8 |
9 | ### Quick Start
10 |
11 | Just fork the repo and then clone your forked repository into your own project folder.
12 |
13 | ```
14 | git clone
15 | cd
16 | npm install
17 | cp .env.example .env
18 | docker-compose up
19 | npm start
20 | ```
21 |
22 | If you want, you can just clone and detach from this repository into your own repository do this:
23 |
24 | ```
25 | git clone https://github.com/heuristicAL/fullstack-ts-react-nest.git
26 | cd
27 | git remote remove origin
28 | git remote add origin YOUR_REPO_URL
29 | git push -u origin master
30 | ```
31 |
32 | ## Why
33 |
34 | - **Simple** to jump into, **Fast** because it is simple.
35 | - Separate `tsconfig.json` for client and server.
36 | - Client and server can share code (And types).
37 | - The client is bundled using [Webpack](https://webpack.github.io/) because it goes to the browser.
38 | - The server is emitted by [TypeScript](https://github.com/Microsoft/TypeScript) because node 6 supports es6.
39 |
40 |
41 |
42 |
43 |
44 | ---
45 |
46 | ### Directory Layout
47 |
48 | ```
49 | .
50 | ├── /node_modules/ # 3rd-party libraries and utilities
51 | ├── /dist/ # All the generated files will go here, and will run from this folder
52 | ├── /assets/ # images, css, jsons etc.
53 | ├── /client/ # React app
54 | ├── /server/ # Express server app.
55 | ├── /spa/ # Global path that loads the client React app.
56 | ├── /api/ # Api REST endpoints.
57 | ├── /shared/ # The shared code between the client and the server
58 | ├── /views/ # the views for the server (currently only loads the react app)
59 | ├── .babelrc # babel configuration
60 | ├── .gitignore # ignored git files and folders
61 | ├── .nvmrc # Force nodejs version
62 | ├── .env # (ignored) Can be used to override environment variables
63 | ├── docker-compose.build.yml # docker-compose for building prod image
64 | ├── docker-compose.yml # docker-compose for running dependencies locally
65 | ├── package.json # The list of 3rd party libraries and utilities
66 | └── tslint.json # TypeScript linting configuration file
67 | └── tsconfig.json # TypeScript configuration file
68 | └── tsconfig.webpack-config.json # TypeScript configuration file
69 | ├── README.md # This file
70 | ```
71 |
72 | ### What's included
73 |
74 | - [React v16](https://facebook.github.io/react/)
75 | - [React router v5](https://github.com/ReactTraining/react-router)
76 | - [Ant Design](https://ant.design/)
77 | - [Jest](https://github.com/facebook/jest)
78 | - [Css modules](https://github.com/css-modules/css-modules)
79 | - [Axios-Observable](https://github.com/zhaosiyang/axios-observable) (For Client/Server communication)
80 | - [NestJs](https://github.com/nestjs/nest)
81 |
82 | ### Usage
83 | > Before running any of the following commands, you should cope `.env.example` to `.env` in the root directory, substituting in your own variables.
84 | > **Read the docker commands below to setup a local DB for the server.**
85 |
86 | - `npm start` - Client and server are in watch mode with source maps, opens [http://localhost:3000](http://localhost:3000)
87 | - `npm run test` - Runs jest tests
88 | - `npm run build` - `dist` folder will include all the needed files, both client (Bundle) and server.
89 | - `npm run prod` - Just runs `node ./dist/server/server.js`, run `npm run build` before this.
90 |
91 | #### Docker commands
92 |
93 | - `docker-compose up` - Spins up a local Postgres image, reading environment variables from the root `.env` file.
94 | - `docker-compose -f ./docker-compose.build.yml build` - Builds a production docker image for the app. **Run the npm build script first**
95 | - `docker-compose -f ./docker-compose.build.yml up` - Spins up the production docker container. Builds it first if it has not been built yet.
96 |
97 | ### Config
98 |
99 | All applications require a config mechanism, for example, `SLACK_API_TOKEN`. Things that you don't want in your git history, you want a different environment to have different value (dev/staging/production). This repo uses the file `config.ts` to access and setup all pre-nest init app variables. And a `.env` file to override variable in dev environment. This file is ignored from git.
100 | Once Nest is up and running, there is a [ConfigService](https://github.com/heuristicAL/fullstack-ts-react-nest/blob/master/server/src/config/config.service.ts) which can be injected on-demand.
101 |
102 | ---
103 |
104 | #### What's not included
105 |
106 | - Universal (Server side rendering)
107 | - Redux/MobX (State management)
108 |
109 | #### Requirements
110 |
111 | - Node 6+
112 |
113 | ---
114 |
115 | #### Licence
116 |
117 | This code is released as is, under MIT licence. Feel free to use it for free for both commercial and private projects. No warranty provided.
118 |
--------------------------------------------------------------------------------
/ant-theme-vars.less:
--------------------------------------------------------------------------------
1 | // ant-default-vars.less
2 | // Available theme variables can be found in
3 | // https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
4 |
5 | @primary-color: #1890ff; // primary color for all components
6 | @link-color: #1890ff; // link color
7 | @success-color: #52c41a; // success state color
8 | @warning-color: #faad14; // warning state color
9 | @error-color: #f5222d; // error state color
10 | @font-size-base: 14px; // major text font size
11 | @heading-color: rgba(0, 0, 0, 0.85); // heading text color
12 | @text-color: rgba(0, 0, 0, 0.65); // major text color
13 | @text-color-secondary : rgba(0, 0, 0, .45); // secondary text color
14 | @disabled-color : rgba(0, 0, 0, .25); // disable state color
15 | @border-radius-base: 4px; // major border radius
16 | @border-color-base: #d9d9d9; // major border color
17 | @box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // major shadow for layers
18 |
--------------------------------------------------------------------------------
/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giard-alexandre/fullstack-ts-react-nest/e9b051bef762faff1277ea9135ba0c405a877a7b/assets/favicon.ico
--------------------------------------------------------------------------------
/assets/images/flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giard-alexandre/fullstack-ts-react-nest/e9b051bef762faff1277ea9135ba0c405a877a7b/assets/images/flow.png
--------------------------------------------------------------------------------
/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giard-alexandre/fullstack-ts-react-nest/e9b051bef762faff1277ea9135ba0c405a877a7b/assets/images/logo.png
--------------------------------------------------------------------------------
/client/About/About.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from 'antd';
2 | import Typography from 'antd/lib/typography';
3 | import React from 'react';
4 |
5 | const {Paragraph} = Typography;
6 |
7 | export const About: React.FC = () => {
8 | return (
9 |
10 |
11 | Heavily Modified to NestJs by Alexandre Giard
12 |
13 |
14 | You can find information at{' '}
15 | https://github.com/gilamran/fullstack-typescript
16 |
17 |
18 | Or, for this specific starter{' '}
19 | https://github.com/heuristicAL/fullstack-ts-react-nest
20 |
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/client/App.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giard-alexandre/fullstack-ts-react-nest/e9b051bef762faff1277ea9135ba0c405a877a7b/client/App.less
--------------------------------------------------------------------------------
/client/App.tsx:
--------------------------------------------------------------------------------
1 | import { notification } from 'antd';
2 | import React from 'react';
3 | import { hot } from 'react-hot-loader';
4 | import { BrowserRouter } from 'react-router-dom';
5 |
6 | import './App.less';
7 | import { AppLayout } from './Layout/AppLayout';
8 | // Pages
9 |
10 | notification.config({
11 | placement: 'topRight',
12 | top: 72,
13 | duration: 5,
14 | });
15 |
16 | const AppImpl: React.FC = () => {
17 | return (
18 |
19 |
22 |
23 | );
24 | };
25 |
26 | export const App = hot(module)(AppImpl);
27 |
--------------------------------------------------------------------------------
/client/Home/Home.less:
--------------------------------------------------------------------------------
1 | .logo {
2 | width: 150px;
3 | padding-bottom: 25px;
4 | }
5 |
--------------------------------------------------------------------------------
/client/Home/Home.tsx:
--------------------------------------------------------------------------------
1 | import { Card, Col, Row } from 'antd';
2 | import Typography from 'antd/lib/typography';
3 | import React from 'react';
4 |
5 | import styles from './Home.less';
6 | import { NotifDemo } from './NotifDemo';
7 |
8 | const { Text } = Typography;
9 | const logoImg = require('../../assets/images/logo.png');
10 |
11 | export const Home: React.FC = () => {
12 | return (
13 |
15 |
16 |
17 |
18 |
19 | This is a starter kit to get you up and running with React &
20 | TypeScript on top of material-ui.
21 |
22 |
23 | You can read more about how to share code between the client and the
24 | server at my{' '}
25 |
26 | medium blog post
27 |
28 | .
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/client/Home/NotifDemo.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Card, notification } from 'antd';
2 | import React from 'react';
3 |
4 | export const NotifDemo: React.FunctionComponent = () => {
5 |
6 | const openNotificationWithIcon = type => {
7 | notification[type]({
8 | message: 'Notification Title',
9 | description:
10 | 'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
11 | });
12 | };
13 |
14 | return (
15 |
16 | openNotificationWithIcon('success')}>Success
17 | openNotificationWithIcon('info')}>Info
18 | openNotificationWithIcon('warning')}>Warning
19 | openNotificationWithIcon('error')}>Error
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/client/Layout/AppHeader.less:
--------------------------------------------------------------------------------
1 | .trigger {
2 | font-size: 18px;
3 | line-height: 64px;
4 | padding: 0 24px;
5 | cursor: pointer;
6 | transition: color 0.3s;
7 | }
8 |
9 | .trigger:hover {
10 | color: #1890ff;
11 | }
12 |
--------------------------------------------------------------------------------
/client/Layout/AppHeader.tsx:
--------------------------------------------------------------------------------
1 | import { Icon, Layout } from 'antd';
2 | import React from 'react';
3 | import styles from './AppHeader.less';
4 | import { ILayoutProps } from './AppLayout';
5 |
6 | const { Header } = Layout;
7 |
8 | export const AppHeader: React.FC = props => {
9 |
10 | const {
11 | collapsed: [drawerCollapsed, setDrawerCollapsed]
12 | } = props.state.drawer;
13 |
14 | const toggle = () => {
15 | setDrawerCollapsed(!drawerCollapsed);
16 | };
17 |
18 | return (
19 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/client/Layout/AppLayout.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giard-alexandre/fullstack-ts-react-nest/e9b051bef762faff1277ea9135ba0c405a877a7b/client/Layout/AppLayout.less
--------------------------------------------------------------------------------
/client/Layout/AppLayout.tsx:
--------------------------------------------------------------------------------
1 | import { Layout } from 'antd';
2 | import React from 'react';
3 | import { Dispatch, SetStateAction } from 'react';
4 | import { AppHeader } from './AppHeader';
5 | import { AppSider } from './AppSider';
6 | import { AppBreadcrumbs } from './Routing/AppBreadcrumbs';
7 | import { AppRouter } from './Routing/AppRouter';
8 |
9 | const {Content, Footer} = Layout;
10 |
11 | export interface ILayoutProps {
12 | state: {
13 | drawer: {
14 | collapsed: [boolean, Dispatch>],
15 | collapsedWidth: [number, Dispatch>]
16 | };
17 | };
18 | }
19 |
20 | export const AppLayout: React.FC = () => {
21 | const drawerState = {
22 | drawer: {
23 | collapsed: React.useState(false),
24 | collapsedWidth: React.useState(80)
25 | }
26 | };
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | ©2019 Created by Alexandre Giard
38 |
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/client/Layout/AppSider.less:
--------------------------------------------------------------------------------
1 | .logo {
2 | height: 32px;
3 | background: rgba(255, 255, 255, 0.2);
4 | margin: 16px;
5 | }
6 |
--------------------------------------------------------------------------------
/client/Layout/AppSider.tsx:
--------------------------------------------------------------------------------
1 | import { Icon, Layout, Menu } from 'antd';
2 | import React, { FC, useEffect, useState } from 'react';
3 | import { matchRoutes } from 'react-router-config';
4 | import { NavLink, withRouter } from 'react-router-dom';
5 | import { ILayoutProps } from './AppLayout';
6 | import styles from './AppSider.less';
7 | import { routesConfig } from './Routing/RouteConfigs';
8 |
9 | const { Sider } = Layout;
10 | const { SubMenu } = Menu;
11 |
12 | export const AppSider: FC = withRouter(props => {
13 | const {
14 | collapsed: [drawerCollapsed, setDrawerCollapsed],
15 | collapsedWidth: [drawerCollapsedWidth, setDrawerCollapsedWidth]
16 | } = props.state.drawer;
17 | const location = props.location;
18 | const [selectedKeys, setSelectedKeys] = useState(['/']);
19 |
20 | const onCollapse = collapsed => {
21 | setDrawerCollapsed(collapsed);
22 | };
23 |
24 | const onBreakpoint = breakpoint => {
25 | setDrawerCollapsedWidth(breakpoint ? 0 : 80);
26 | };
27 |
28 | useEffect(() => {
29 | // Get the route info
30 | const branch = matchRoutes(routesConfig, location.pathname);
31 | const selectedRoutes = branch.map(r => r.match.url);
32 | setSelectedKeys(selectedRoutes);
33 | }, [location.pathname]);
34 |
35 | return (
36 |
43 |
44 |
48 |
49 |
50 |
51 | Home
52 |
53 |
54 |
55 |
56 |
57 | About
58 |
59 |
60 |
61 |
62 |
63 | Users
64 |
65 |
66 |
70 |
71 | Admin
72 |
73 | }
74 | >
75 |
76 |
77 |
78 | Nothing
79 |
80 |
81 |
82 |
83 |
84 | Should
85 |
86 |
87 |
88 |
89 |
90 | Happen
91 |
92 |
93 |
94 |
95 |
96 | );
97 | });
98 |
--------------------------------------------------------------------------------
/client/Layout/Routing/AppBreadcrumbs.tsx:
--------------------------------------------------------------------------------
1 | import { Breadcrumb } from 'antd';
2 | import React, { ComponentClass } from 'react';
3 | import withBreadcrumbs, { BreadcrumbsRoute } from 'react-router-breadcrumbs-hoc';
4 | import { NavLink } from 'react-router-dom';
5 |
6 | import { routesConfig } from './RouteConfigs';
7 |
8 | export const AppBreadcrumbs: ComponentClass = withBreadcrumbs(routesConfig as BreadcrumbsRoute[])(({ breadcrumbs }) => {
9 | return (
10 |
11 | {breadcrumbs.map(({
12 | match,
13 | breadcrumb
14 | // other props are available during render, such as `location`
15 | // and any props found in your route objects will be passed through too
16 | }) => (
17 |
18 | {breadcrumb}
19 |
20 | ))}
21 |
22 | );
23 | });
24 |
--------------------------------------------------------------------------------
/client/Layout/Routing/AppRouter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch } from 'react-router-dom';
3 | import { renderRoutes } from 'react-router-config';
4 | import { routesConfig } from './RouteConfigs';
5 |
6 | export const AppRouter: React.FC = () => {
7 | return (
8 |
9 |
10 | {renderRoutes(routesConfig)}
11 |
12 |
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/client/Layout/Routing/RouteConfigs.ts:
--------------------------------------------------------------------------------
1 | import { BreadcrumbsRoute } from 'react-router-breadcrumbs-hoc';
2 | import { RouteConfig } from 'react-router-config';
3 | import { About } from '../../About/About';
4 | import { Home } from '../../Home/Home';
5 | import { UserList } from '../../User/UserList';
6 |
7 | export const routesConfig: Array> = [
8 | {
9 | path: '/',
10 | component: Home,
11 | exact: true,
12 | breadcrumb: 'Home'
13 | },
14 | {
15 | path: '/about',
16 | component: About
17 | },
18 | {
19 | path: '/users',
20 | component: UserList,
21 | breadcrumb: 'Users',
22 | routes: [
23 | {
24 | path: '/users/:userName'
25 | }
26 | ]
27 | }
28 | ];
29 |
--------------------------------------------------------------------------------
/client/User/UserList.tsx:
--------------------------------------------------------------------------------
1 | import { Divider, Table, Tag } from 'antd';
2 | import React from 'react';
3 | import { NavLink } from 'react-router-dom';
4 |
5 | const columns = [
6 | {
7 | title: 'Name',
8 | dataIndex: 'name',
9 | key: 'name',
10 | render: text => {text} ,
11 | },
12 | {
13 | title: 'Age',
14 | dataIndex: 'age',
15 | key: 'age',
16 | },
17 | {
18 | title: 'Address',
19 | dataIndex: 'address',
20 | key: 'address',
21 | },
22 | {
23 | title: 'Tags',
24 | key: 'tags',
25 | dataIndex: 'tags',
26 | render: tags => (
27 |
28 | {tags.map(tag => {
29 | let color = tag.length > 5 ? 'geekblue' : 'green';
30 | if (tag === 'loser') {
31 | color = 'volcano';
32 | }
33 | return (
34 |
35 | {tag.toUpperCase()}
36 |
37 | );
38 | })}
39 |
40 | ),
41 | },
42 | {
43 | title: 'Action',
44 | key: 'action',
45 | render: (text, record) => (
46 |
47 | Invite {record.name}
48 |
49 | Delete
50 |
51 | ),
52 | },
53 | ];
54 |
55 | const data = [
56 | {
57 | key: '1',
58 | name: 'John Brown',
59 | age: 32,
60 | address: 'New York No. 1 Lake Park',
61 | tags: ['nice', 'developer'],
62 | },
63 | {
64 | key: '2',
65 | name: 'Jim Green',
66 | age: 42,
67 | address: 'London No. 1 Lake Park',
68 | tags: ['loser'],
69 | },
70 | {
71 | key: '3',
72 | name: 'Joe Black',
73 | age: 32,
74 | address: 'Sidney No. 1 Lake Park',
75 | tags: ['cool', 'teacher'],
76 | },
77 | ];
78 |
79 | export const UserList: React.FC = () => {
80 | return ();
81 | };
82 |
--------------------------------------------------------------------------------
/client/client.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Favicon from 'react-favicon';
4 | // noinspection ES6UnusedImports
5 | import { hot } from 'react-hot-loader';
6 | import { App } from './App';
7 |
8 | const favicon = require('../assets/images/logo.png');
9 |
10 | // const theme = createMuiTheme({
11 | // palette: {
12 | // primary: indigo,
13 | // secondary: red,
14 | // }
15 | // });
16 |
17 | ReactDOM.render(
18 |
19 |
20 |
21 |
22 | , document.getElementById('app'));
23 |
--------------------------------------------------------------------------------
/client/setupEnzyme.ts:
--------------------------------------------------------------------------------
1 | import { configure } from 'enzyme';
2 | import * as EnzymeAdapter from 'enzyme-adapter-react-16';
3 |
4 | configure({adapter: new EnzymeAdapter()});
5 |
--------------------------------------------------------------------------------
/client/tsconfig.client.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "emitDecoratorMetadata": true,
5 | "paths": {
6 | "typeorm": ["../node_modules/typeorm/typeorm-model-shim.js"]
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/client/utils/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giard-alexandre/fullstack-ts-react-nest/e9b051bef762faff1277ea9135ba0c405a877a7b/client/utils/.gitkeep
--------------------------------------------------------------------------------
/docker-compose.build.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | cm:
5 | build: .
6 | # volumes: # Possibly only useful for developing inside docker?
7 | # - .:/usr/src/app
8 | # - /usr/src/app/node_modules
9 | ports:
10 | - ${APP_PORT}:3001
11 | - 9229:9229
12 | depends_on:
13 | - cm_db
14 | environment:
15 | DB_HOST: cm_db
16 | APP_PORT:
17 | DB_PORT: 5432
18 | DB_USERNAME:
19 | DB_PASSWORD:
20 | DB_DATABASE:
21 | cm_db:
22 | image: postgres:11.2-alpine
23 | ports:
24 | - "${DB_PORT}:5432"
25 | volumes:
26 | - dbdata:/var/lib/postgresql/data
27 | environment:
28 | POSTGRES_PASSWORD: ${DB_PASSWORD}
29 | POSTGRES_USER: ${DB_USERNAME}
30 | POSTGRES_DB: ${DB_DATABASE}
31 |
32 | volumes:
33 | dbdata:
34 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | cm_db:
5 | image: postgres:11.2-alpine
6 | ports:
7 | - "${DB_PORT}:5432"
8 | volumes:
9 | - dbdata:/var/lib/postgresql/data
10 | environment:
11 | POSTGRES_PASSWORD: ${DB_PASSWORD}
12 | POSTGRES_USER: ${DB_USERNAME}
13 | POSTGRES_DB: ${DB_DATABASE}
14 |
15 | volumes:
16 | dbdata:
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | require('./dist/server/src/main');
2 |
--------------------------------------------------------------------------------
/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "language": "ts",
3 | "collection": "@nestjs/schematics",
4 | "sourceRoot": "server/src/api"
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fullstack-ts-react-nest",
3 | "version": "1.0.0",
4 | "description": "FullStack React with TypeScript and NestJS starter kit.",
5 | "main": "index.js",
6 | "engines": {
7 | "node": ">=6.9.5"
8 | },
9 | "scripts": {
10 | "clean": "rimraf dist",
11 | "copy:views": "copyfiles views/*.ejs dist\n",
12 | "start": "npm run clean && npm run copy:views && concurrently --prefix \"[{name}]\" --names \"SERVER,CLIENT\" -c \"bgBlue.bold,bgGreen.bold\" \"npm run dev-server\" \"npm run dev-client\"",
13 | "start:hot": "npm run clean && npm run copy:views && concurrently --prefix \"[{name}]\" --names \"SERVER,CLIENT\" -c \"bgBlue.bold,bgGreen.bold\" \"npm run dev-server\" \"npm run dev-client:hot\"",
14 | "prestart:prod": "rimraf dist && npm run build",
15 | "start:prod": "cross-env NODE_ENV=production node index.js",
16 | "dev-client": "cross-env TS_NODE_PROJECT=\"tsconfig.webpack-config.json\" webpack-dev-server -w",
17 | "dev-client:hot": "cross-env TS_NODE_PROJECT=\"tsconfig.webpack-config.json\" webpack-dev-server -w --hot",
18 | "dev-server": "tsc-watch -p ./server --onSuccess \"node --inspect index.js\"",
19 | "lint": "tslint -c tslint.json 'src/**/*.ts' 'src/**/*.tsx'",
20 | "prod": "node index.js",
21 | "type-check": "tsc -p ./tsconfig.json",
22 | "build-client": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig.webpack-config.json\" webpack -p",
23 | "build-server": "tsc -p ./server",
24 | "build": "npm run clean && npm run copy:views && concurrently --prefix \"[{name}]\" --names \"SERVER,CLIENT\" -c \"bgBlue.bold,bgGreen.bold\" \"npm run build-server\" \"npm run build-client\"",
25 | "test": "jest",
26 | "test:watch": "jest --watch",
27 | "test:cov": "jest --coverage",
28 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register jest --runInBand",
29 | "test:e2e": "jest --config ./test/jest-e2e.json"
30 | },
31 | "prettier": {
32 | "trailingComma": "all",
33 | "tabWidth": 2,
34 | "semi": true,
35 | "singleQuote": true,
36 | "jsxSingleQuote": true,
37 | "printWidth": 120
38 | },
39 | "keywords": [
40 | "typescript",
41 | "react",
42 | "starter-kit",
43 | "webpack",
44 | "fullstack",
45 | "express",
46 | "express4",
47 | "node"
48 | ],
49 | "repository": {
50 | "type": "git",
51 | "url": "https://github.com/gilamran/fullstack-typescript.git"
52 | },
53 | "license": "MIT",
54 | "author": "Gil Amran",
55 | "jest": {
56 | "roots": [
57 | "/server",
58 | "/client"
59 | ],
60 | "transform": {
61 | "^.+\\.tsx?$": "ts-jest"
62 | },
63 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
64 | "snapshotSerializers": [
65 | "enzyme-to-json/serializer"
66 | ],
67 | "setupFilesAfterEnv": [
68 | "/client/setupEnzyme.ts"
69 | ],
70 | "moduleFileExtensions": [
71 | "ts",
72 | "tsx",
73 | "js",
74 | "jsx",
75 | "json",
76 | "node"
77 | ],
78 | "globals": {
79 | "ts-jest": {
80 | "tsConfig": "/server/tsconfig.test.json"
81 | }
82 | }
83 | },
84 | "dependencies": {
85 | "@babel/polyfill": "^7.4.4",
86 | "@nestjs/common": "^6.6.7",
87 | "@nestjs/core": "^6.6.7",
88 | "@nestjs/platform-express": "^6.6.7",
89 | "@nestjs/typeorm": "^6.1.3",
90 | "@types/react-router": "^5.0.3",
91 | "@types/react-router-config": "^5.0.0",
92 | "antd": "~3.23.2",
93 | "axios-observable": "^1.1.2",
94 | "babel-plugin-react-css-modules": "^5.2.6",
95 | "babel-plugin-transform-typescript-metadata": "^0.2.2",
96 | "body-parser": "^1.19.0",
97 | "clsx": "^1.0.4",
98 | "css-modules": "^0.3.0",
99 | "date-fns": "^2.0.1",
100 | "dotenv": "^8.1.0",
101 | "ejs": "^2.7.1",
102 | "express": "^4.17.1",
103 | "find-up": "^4.1.0",
104 | "less": "~2.7.3",
105 | "less-vars-to-js": "^1.3.0",
106 | "nest-router": "^1.0.9",
107 | "pg": "^7.12.1",
108 | "react-favicon": "0.0.17",
109 | "react-router-breadcrumbs-hoc": "^3.2.3",
110 | "react-router-config": "^5.0.1",
111 | "reflect-metadata": "^0.1.13",
112 | "rxjs": "^6.5.3",
113 | "tsconfig-paths-webpack-plugin": "^3.2.0",
114 | "tslib": "^1.10.0",
115 | "typeorm": "^0.2.18"
116 | },
117 | "devDependencies": {
118 | "@babel/core": "^7.5.5",
119 | "@babel/plugin-proposal-class-properties": "^7.5.5",
120 | "@babel/plugin-proposal-decorators": "^7.6.0",
121 | "@babel/plugin-proposal-numeric-separator": "^7.2.0",
122 | "@babel/plugin-proposal-object-rest-spread": "^7.5.5",
123 | "@babel/plugin-transform-runtime": "^7.5.5",
124 | "@babel/preset-env": "^7.5.5",
125 | "@babel/preset-react": "^7.0.0",
126 | "@babel/preset-typescript": "^7.3.3",
127 | "@hot-loader/react-dom": "^16.9.0",
128 | "@nestjs/testing": "^6.6.7",
129 | "@types/copy-webpack-plugin": "^5.0.0",
130 | "@types/cssnano": "^4.0.0",
131 | "@types/enzyme": "^3.10.3",
132 | "@types/express": "^4.17.1",
133 | "@types/jest": "^24.0.18",
134 | "@types/node": "^10.14.17",
135 | "@types/react": "^16.9.2",
136 | "@types/supertest": "^2.0.8",
137 | "@types/webpack": "^4.39.1",
138 | "@types/webpack-bundle-analyzer": "^2.13.2",
139 | "@types/webpack-dev-server": "^3.1.7",
140 | "@types/webpack-manifest-plugin": "^2.0.0",
141 | "babel-loader": "^8.0.6",
142 | "babel-plugin-import": "^1.12.1",
143 | "concurrently": "^4.1.2",
144 | "copy-webpack-plugin": "^5.0.4",
145 | "copyfiles": "^2.1.1",
146 | "cross-env": "^5.2.1",
147 | "css-loader": "^2.1.1",
148 | "cssnano": "^4.1.10",
149 | "enzyme": "^3.10.0",
150 | "enzyme-adapter-react-16": "^1.14.0",
151 | "enzyme-to-json": "^3.4.0",
152 | "file-loader": "^4.2.0",
153 | "http-proxy-middleware": "^0.20.0",
154 | "jest": "^24.9.0",
155 | "less-loader": "^5.0.0",
156 | "open-browser-webpack-plugin": "^0.0.5",
157 | "postcss-loader": "^3.0.0",
158 | "react": "^16.9.0",
159 | "react-dom": "^16.9.0",
160 | "react-hot-loader": "^4.12.12",
161 | "react-router": "^5.0.1",
162 | "react-router-dom": "^5.0.1",
163 | "rimraf": "^3.0.0",
164 | "style-loader": "^1.0.0",
165 | "ts-jest": "^24.0.2",
166 | "ts-node": "^8.3.0",
167 | "tsc-watch": "^3.0.0",
168 | "tsconfig-paths": "^3.8.0",
169 | "tslint": "^5.19.0",
170 | "typescript": "~3.5.3",
171 | "url-loader": "^2.1.0",
172 | "webpack": "^4.39.3",
173 | "webpack-bundle-analyzer": "^3.4.1",
174 | "webpack-cli": "^3.3.7",
175 | "webpack-dev-server": "^3.8.0",
176 | "webpack-manifest-plugin": "^2.0.4"
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/server/config.ts:
--------------------------------------------------------------------------------
1 | const IS_DEV = process.env.NODE_ENV !== 'production';
2 |
3 | const findUp = require('find-up');
4 |
5 | if (IS_DEV) {
6 | require('dotenv').config({path: findUp.sync('.env')});
7 | }
8 |
9 | const {version: VERSION} = require(findUp.sync('package.json'));
10 |
11 | // server
12 | const SERVER_PORT = process.env.APP_PORT || 3001; // TODO: Crash if port is not specified?
13 | const WEBPACK_PORT = 8086; // For dev environment only
14 |
15 | export {
16 | IS_DEV,
17 | VERSION,
18 | SERVER_PORT,
19 | WEBPACK_PORT,
20 | };
21 |
--------------------------------------------------------------------------------
/server/src/api/api.module.ts:
--------------------------------------------------------------------------------
1 | import { HttpModule, Module } from '@nestjs/common';
2 | import { RouterModule } from 'nest-router';
3 | import { GlobalModule } from './global';
4 | import { UserModule } from './user/user.module';
5 |
6 | @Module({
7 | imports: [
8 | RouterModule.forRoutes([{
9 | path: '/api',
10 | module: ApiModule,
11 | children: [
12 | UserModule
13 | ]
14 | }]),
15 | HttpModule,
16 | GlobalModule,
17 | UserModule,
18 | ],
19 | controllers: [],
20 | providers: [],
21 |
22 | })
23 | export class ApiModule {
24 | }
25 |
--------------------------------------------------------------------------------
/server/src/api/global/auth/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
2 | import { Observable } from 'rxjs';
3 |
4 | @Injectable()
5 | export class AuthGuard implements CanActivate {
6 | canActivate(
7 | context: ExecutionContext,
8 | ): boolean | Promise | Observable {
9 | const request = context.switchToHttp().getRequest();
10 | // This just always returns true as the request object should always be present.
11 | return !!request;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/server/src/api/global/auth/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { APP_GUARD } from '@nestjs/core';
3 | import { AuthGuard } from './auth.guard';
4 |
5 | @Module({
6 | imports: [],
7 | controllers: [],
8 | providers: [{
9 | provide: APP_GUARD,
10 | useClass: AuthGuard,
11 | }],
12 | })
13 | export class AuthModule {
14 | }
15 |
--------------------------------------------------------------------------------
/server/src/api/global/config/config.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { ConfigService } from './config.service';
3 |
4 | describe('ConfigService', () => {
5 | let service: ConfigService;
6 |
7 | beforeEach(async () => {
8 | process.env = {Key: 'Key_Value'};
9 | const module: TestingModule = await Test.createTestingModule({
10 | providers: [ConfigService],
11 | }).compile();
12 |
13 | service = module.get(ConfigService);
14 | });
15 |
16 | it('should be defined', () => {
17 | expect(service).toBeDefined();
18 | });
19 | it('should return "Key_Value"', () => {
20 | expect(service.getString('Key')).toBe('Key_Value');
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/server/src/api/global/config/config.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 |
3 | import * as dotenv from 'dotenv';
4 | import * as findUp from 'find-up';
5 |
6 | const IS_DEV = process.env.NODE_ENV !== 'production';
7 |
8 | @Injectable()
9 | export class ConfigService {
10 | private readonly envConfig: { [key: string]: string };
11 |
12 | constructor() {
13 | if (IS_DEV) {
14 | dotenv.config(findUp.sync('.env'));
15 | }
16 | this.envConfig = process.env;
17 | }
18 |
19 | getString(key: string): string {
20 | return this.envConfig[key];
21 | }
22 |
23 | getNumber(key: string): number {
24 | return parseInt(this.envConfig[key], 10);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/src/api/global/config/db-config/db-config.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
3 | import { ConfigService } from '../config.service';
4 | import { DbConfigService } from './db-config.service';
5 |
6 | describe('DbConfigService', () => {
7 | let service: DbConfigService;
8 | let configService: ConfigService;
9 |
10 | beforeEach(async () => {
11 | const module: TestingModule = await Test.createTestingModule({
12 | providers: [ConfigService,
13 | DbConfigService],
14 | }).overrideProvider(ConfigService).useValue(
15 | {
16 | getString: () => 'FakeConfig',
17 | getNumber: () => 1234
18 | }
19 | ).compile();
20 |
21 | configService = module.get(ConfigService);
22 | service = module.get(DbConfigService);
23 | });
24 |
25 | it('should be defined', () => {
26 | expect(service).toBeDefined();
27 | });
28 | it('should return db config for postgres', () => {
29 | // jest.spyOn(configService, 'getString').mockImplementation(() => 'FakeConfig');
30 | // jest.spyOn(configService, 'getNumber').mockImplementation(() => 1234);
31 | const dbConfig = service.createTypeOrmOptions() as PostgresConnectionOptions;
32 | expect(dbConfig.type).toBe('postgres');
33 | expect(dbConfig.host).toBe('FakeConfig');
34 | expect(dbConfig.port).toBe(1234);
35 | expect(dbConfig.username).toBe('FakeConfig');
36 | expect(dbConfig.password).toBe('FakeConfig');
37 | expect(dbConfig.database).toBe('FakeConfig');
38 | expect(dbConfig.synchronize).toBeTruthy();
39 | expect(dbConfig.password).toBe('FakeConfig');
40 | });
41 | it('should return db config for postgres and database with "NewConfig" for strings', () => {
42 | jest.spyOn(configService, 'getString').mockImplementation(() => 'NewConfig');
43 | const dbConfig = service.createTypeOrmOptions() as PostgresConnectionOptions;
44 | expect(dbConfig.type).toBe('postgres');
45 | expect(dbConfig.host).toBe('NewConfig');
46 | expect(dbConfig.port).toBe(1234);
47 | expect(dbConfig.username).toBe('NewConfig');
48 | expect(dbConfig.password).toBe('NewConfig');
49 | expect(dbConfig.database).toBe('NewConfig');
50 | expect(dbConfig.synchronize).toBeTruthy();
51 | expect(dbConfig.password).toBe('NewConfig');
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/server/src/api/global/config/db-config/db-config.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';
3 | import { ConfigService } from '../config.service';
4 |
5 | @Injectable()
6 | export class DbConfigService implements TypeOrmOptionsFactory {
7 | constructor(private readonly config: ConfigService) {
8 | }
9 |
10 | createTypeOrmOptions(): TypeOrmModuleOptions {
11 | return {
12 | type: 'postgres',
13 | host: this.config.getString('DB_HOST'),
14 | port: this.config.getNumber('DB_PORT'),
15 | username: this.config.getString('DB_USERNAME'),
16 | password: this.config.getString('DB_PASSWORD'),
17 | database: this.config.getString('DB_DATABASE'),
18 | entities: [__dirname + '/**/*.entity{.ts,.js}'],
19 | synchronize: true,
20 | } as TypeOrmModuleOptions;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/server/src/api/global/global.module.ts:
--------------------------------------------------------------------------------
1 | import { Global, HttpModule, Module } from '@nestjs/common';
2 | import { Agent } from 'https';
3 | import { ConfigService } from './config/config.service';
4 | import { DbConfigService } from './config/db-config/db-config.service';
5 |
6 | @Global()
7 | @Module({
8 | imports: [
9 | HttpModule.register({
10 | timeout: 5000,
11 | maxRedirects: 5,
12 | httpsAgent: new Agent({
13 | rejectUnauthorized: false
14 | })
15 | }),
16 | ],
17 | providers: [ConfigService, DbConfigService],
18 | exports: [ConfigService, DbConfigService]
19 | })
20 | export class GlobalModule {
21 | }
22 |
--------------------------------------------------------------------------------
/server/src/api/global/index.ts:
--------------------------------------------------------------------------------
1 | export * from './config/db-config/db-config.service';
2 | export * from './config/config.service';
3 | export * from './global.module';
4 |
--------------------------------------------------------------------------------
/server/src/api/user/controllers/v1.user.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { V1UserController } from './v1.user.controller';
3 |
4 | describe('User Controller', () => {
5 | let controller: V1UserController;
6 |
7 | beforeEach(async () => {
8 | const module: TestingModule = await Test.createTestingModule({
9 | controllers: [V1UserController],
10 | }).compile();
11 |
12 | controller = module.get(V1UserController);
13 | });
14 |
15 | it('should be defined', () => {
16 | expect(controller).toBeDefined();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/server/src/api/user/controllers/v1.user.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get } from '@nestjs/common';
2 | import { User } from '@shared/entities/user/user.entity';
3 | import { Observable, of } from 'rxjs';
4 | import { UserService } from '../services/user.service';
5 |
6 | @Controller('v1/user')
7 | export class V1UserController {
8 | constructor(private readonly userService: UserService) {
9 | }
10 |
11 | @Get()
12 | getTasks(): Observable {
13 | return of(this.userService.getAlUsers());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/server/src/api/user/services/user.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import {UserService} from './user.service';
3 |
4 | describe('UserService', () => {
5 | let service: UserService;
6 |
7 | beforeEach(async () => {
8 | const module: TestingModule = await Test.createTestingModule({
9 | providers: [UserService],
10 | }).compile();
11 |
12 | service = module.get(UserService);
13 | });
14 |
15 | it('should be defined', () => {
16 | expect(service).toBeDefined();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/server/src/api/user/services/user.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { User } from '../../../../../shared/entities/user/user.entity';
3 |
4 | @Injectable()
5 | export class UserService {
6 | getAlUsers(): User[] {
7 | return [new User({defaultColor: 'somehitng', defaultFont: 'sm,ehtun', name: {first: 'first', last: 'last'}})];
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/server/src/api/user/user.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { V1UserController } from './controllers/v1.user.controller';
3 | import { UserService } from './services/user.service';
4 |
5 | @Module({
6 | controllers: [V1UserController],
7 | providers: [UserService],
8 | })
9 | export class UserModule {
10 | }
11 |
--------------------------------------------------------------------------------
/server/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import {Module} from '@nestjs/common';
2 | import {ApiModule} from './api/api.module';
3 | import {SpaModule} from './spa/spaModule';
4 |
5 | /**
6 | * This file should theoretically never change.
7 | * Thank you
8 | */
9 | @Module({
10 | imports: [
11 | ApiModule,
12 | SpaModule,
13 | ],
14 | })
15 | export class AppModule {
16 | }
17 |
--------------------------------------------------------------------------------
/server/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { NestExpressApplication } from '@nestjs/platform-express';
3 | import { join } from 'path';
4 | import { SERVER_PORT } from '../config';
5 | import { AppModule } from './app.module';
6 | import { staticsRouter } from './statics-router';
7 |
8 | async function bootstrap() {
9 | const app = await NestFactory.create(AppModule);
10 |
11 | app.setViewEngine('ejs');
12 |
13 | app.useStaticAssets(join(__dirname, '../..', 'assets'));
14 | app.setBaseViewsDir(join(__dirname, '../..', 'views'));
15 |
16 | app.use(staticsRouter());
17 |
18 | await app.listen(SERVER_PORT);
19 | }
20 |
21 | bootstrap();
22 |
--------------------------------------------------------------------------------
/server/src/spa/app.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { SpaController } from './spa.controller';
3 |
4 | describe('SpaController', () => {
5 | let appController: SpaController;
6 |
7 | beforeEach(async () => {
8 | const app: TestingModule = await Test.createTestingModule({
9 | controllers: [SpaController],
10 | providers: [],
11 | }).compile();
12 |
13 | appController = app.get(SpaController);
14 | });
15 |
16 | describe('root', () => {
17 | it('should be defined', () => {
18 | expect(appController).toBeDefined();
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/server/src/spa/manifest-manager/manifest-manager.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { ManifestManagerService } from './manifest-manager.service';
3 |
4 | describe('ManifestManagerService', () => {
5 | let service: ManifestManagerService;
6 |
7 | beforeEach(async () => {
8 | const module: TestingModule = await Test.createTestingModule({
9 | providers: [ManifestManagerService],
10 | }).compile();
11 |
12 | service = module.get(ManifestManagerService);
13 | });
14 |
15 | it('should be defined', () => {
16 | expect(service).toBeDefined();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/server/src/spa/manifest-manager/manifest-manager.service.ts:
--------------------------------------------------------------------------------
1 | import { HttpService, Injectable } from '@nestjs/common';
2 | import * as fs from 'fs';
3 | import * as path from 'path';
4 | import { IS_DEV, WEBPACK_PORT } from '../../../config';
5 |
6 | export interface IManifest {
7 | 'main.js': string;
8 | 'vendors.js': string;
9 | 'logo.png': string;
10 | }
11 |
12 | @Injectable()
13 | export class ManifestManagerService {
14 | constructor(private readonly http: HttpService) {
15 | }
16 |
17 | public async getManifest(): Promise {
18 | let manifest: IManifest;
19 | if (IS_DEV) {
20 | // load from webpack dev server
21 | manifest = await this.getManifestFromWebpack();
22 | } else {
23 | // read from file system
24 | const manifestStr = fs.readFileSync(path.join(process.cwd(), 'dist', 'statics', 'manifest.json'), 'utf-8').toString();
25 | manifest = JSON.parse(manifestStr);
26 | }
27 | return manifest;
28 | }
29 |
30 | private getManifestFromWebpack(): Promise {
31 | return new Promise((resolve, reject) => {
32 | this.http.get(`http://localhost:${WEBPACK_PORT}/statics/manifest.json`).subscribe(
33 | res => resolve(res.data as IManifest),
34 | error => reject(error)
35 | );
36 | });
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/src/spa/spa.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Render } from '@nestjs/common';
2 | import { ManifestManagerService } from './manifest-manager/manifest-manager.service';
3 |
4 | @Controller()
5 | export class SpaController {
6 | constructor(private manifestManager: ManifestManagerService) {
7 | }
8 |
9 | @Get('**')
10 | @Render('page')
11 | async root() {
12 | const manifest = await this.manifestManager.getManifest();
13 | return {manifest};
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/server/src/spa/spaModule.ts:
--------------------------------------------------------------------------------
1 | import { HttpModule, Module } from '@nestjs/common';
2 | import { TypeOrmModule } from '@nestjs/typeorm';
3 | import { GlobalModule, DbConfigService } from '../api/global';
4 | import { ManifestManagerService } from './manifest-manager/manifest-manager.service';
5 | import { SpaController } from './spa.controller';
6 |
7 | @Module({
8 | imports: [
9 | HttpModule,
10 | GlobalModule,
11 | TypeOrmModule.forRootAsync({
12 | imports: [GlobalModule],
13 | useExisting: DbConfigService,
14 | }),
15 | ],
16 | controllers: [SpaController],
17 | providers: [ManifestManagerService],
18 | })
19 | export class SpaModule {
20 | }
21 |
--------------------------------------------------------------------------------
/server/src/statics-router.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express';
2 | import { Router } from 'express';
3 | import * as path from 'path';
4 | import { IS_DEV, WEBPACK_PORT } from '../config';
5 |
6 | export function staticsRouter() {
7 | const router = Router();
8 |
9 | if (IS_DEV) {
10 | const proxy = require('http-proxy-middleware');
11 | // All the assets are hosted by Webpack on localhost:${config.WEBPACK_PORT} (Webpack-dev-server)
12 | router.use(
13 | '/statics',
14 | proxy({
15 | target: `http://localhost:${WEBPACK_PORT}/`,
16 | }),
17 | );
18 | } else {
19 | const staticsPath = path.join(process.cwd(), 'dist', 'statics');
20 |
21 | // All the assets are in "statics" folder (Done by Webpack during the build phase)
22 | router.use('/statics', express.static(staticsPath));
23 | }
24 | return router;
25 | }
26 |
--------------------------------------------------------------------------------
/server/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import * as request from 'supertest';
3 | import { ApiModule } from '../src/api/api.module';
4 |
5 | describe('AppController (e2e)', () => {
6 | let app;
7 |
8 | beforeEach(async () => {
9 | const moduleFixture: TestingModule = await Test.createTestingModule({
10 | imports: [ApiModule],
11 | }).compile();
12 |
13 | app = moduleFixture.createNestApplication();
14 | await app.init();
15 | });
16 |
17 | it('/ (GET)', () => {
18 | return request(app.getHttpServer())
19 | .get('/')
20 | .expect(200)
21 | .expect('Hello World!');
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/server/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": [
3 | "js",
4 | "json",
5 | "ts"
6 | ],
7 | "rootDir": ".",
8 | "testEnvironment": "node",
9 | "testRegex": ".e2e-spec.ts$",
10 | "transform": {
11 | "^.+\\.(t|j)s$": "ts-jest"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/server/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "node_modules",
5 | "test",
6 | "dist",
7 | "**/*spec.ts"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "sourceMap": true,
5 | "module": "commonjs",
6 | "declaration": true,
7 | "removeComments": true,
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "importHelpers": true,
11 | "outDir": "../dist",
12 | "rootDir": "..",
13 | "lib": [
14 | "es6"
15 | ],
16 | "moduleResolution": "node",
17 | "types": [
18 | "node"
19 | ],
20 | "baseUrl": ".",
21 | "incremental": true,
22 | "paths": {
23 | "@shared/*": [
24 | "../shared/*"
25 | ]
26 | }
27 | },
28 | "files": [
29 | "./src/main.ts"
30 | ],
31 | "exclude": [
32 | "../node_modules"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/server/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es6",
5 | "module": "commonjs",
6 | "moduleResolution": "node",
7 | "lib": [
8 | "es6",
9 | "dom"
10 | ],
11 | "jsx": "react",
12 | "types": [
13 | "node",
14 | "jest"
15 | ],
16 | "noEmit": true
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/shared/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giard-alexandre/fullstack-ts-react-nest/e9b051bef762faff1277ea9135ba0c405a877a7b/shared/.gitkeep
--------------------------------------------------------------------------------
/shared/entities/entityBase.ts:
--------------------------------------------------------------------------------
1 | import { CreateDateColumn, PrimaryGeneratedColumn, UpdateDateColumn, VersionColumn } from 'typeorm';
2 |
3 | /**
4 | * Abstract class to define the base fields and columns for an Entity.
5 | * @param T The type of the child class inheriting from {@link BaseEntity}
6 | */
7 | export abstract class EntityBase {
8 | @PrimaryGeneratedColumn()
9 | id: number;
10 |
11 | @CreateDateColumn()
12 | createdAt: Date;
13 |
14 | @UpdateDateColumn()
15 | updatedAt: Date;
16 |
17 | @VersionColumn()
18 | version: number;
19 |
20 | /**
21 | * @param {T} init The initialization parameters for the entity.
22 | * @returns {T} New instance of type {@link T}.
23 | */
24 | constructor(init?: Partial) {
25 | Object.assign(this, init);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/shared/entities/partials/name.ts:
--------------------------------------------------------------------------------
1 | import { Column } from 'typeorm';
2 |
3 | export class Name {
4 | @Column()
5 | first: string;
6 |
7 | @Column()
8 | last: string;
9 | }
10 |
--------------------------------------------------------------------------------
/shared/entities/user/user.entity.ts:
--------------------------------------------------------------------------------
1 | import { Column, Entity } from 'typeorm';
2 | import { EntityBase } from '../entityBase';
3 | import { Name } from '../partials/name';
4 |
5 | @Entity()
6 | export class User extends EntityBase {
7 |
8 | @Column(type => Name)
9 | name: Name;
10 |
11 | @Column()
12 | defaultFont: string;
13 |
14 | @Column()
15 | defaultColor: string;
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "moduleResolution": "node",
5 | "sourceMap": true,
6 | "noEmit": true,
7 | "skipLibCheck": true,
8 | "experimentalDecorators": true,
9 | "allowSyntheticDefaultImports": true,
10 | "isolatedModules": true,
11 | "outDir": "./dist",
12 | "lib": [
13 | "es2015",
14 | "dom"
15 | ],
16 | "jsx": "react",
17 | "baseUrl": ".",
18 | "paths": {
19 | "@shared/*": [
20 | "./shared/*"
21 | ]
22 | }
23 | },
24 | "files": [
25 | "./types.d.ts",
26 | "./client/client.tsx"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/tsconfig.webpack-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "esModuleInterop": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "jsRules": {},
7 | "rules": {
8 | "eofline": true,
9 | "max-line-length": [
10 | true,
11 | 150
12 | ],
13 | "quotemark": [
14 | true,
15 | "single",
16 | "jsx-single"
17 | ],
18 | "arrow-parens": [
19 | true,
20 | "ban-single-arg-parens"
21 | ],
22 | "trailing-comma": [
23 | false,
24 | {
25 | "multiline": "always",
26 | "singleline": "always"
27 | }
28 | ],
29 | "ordered-imports": false,
30 | "object-literal-sort-keys": false,
31 | "interface-name": [
32 | true
33 | ],
34 | "member-access": [
35 | false
36 | ],
37 | "no-console": [
38 | false,
39 | "log",
40 | "error"
41 | ],
42 | "no-var-requires": false
43 | },
44 | "rulesDirectory": []
45 | }
46 |
--------------------------------------------------------------------------------
/types.d.ts:
--------------------------------------------------------------------------------
1 | declare var require: (path: string) => any;
2 | declare var module: any;
3 | declare module '*.less';
4 | declare module '*.svg';
5 |
--------------------------------------------------------------------------------
/views/page.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | TypeScript and React
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/webpack.config.ts:
--------------------------------------------------------------------------------
1 | const cssnano = require('cssnano');
2 | import * as fs from 'fs';
3 | const OpenBrowserPlugin = require('open-browser-webpack-plugin');
4 | import * as path from 'path';
5 | import * as webpack from 'webpack';
6 | // noinspection ES6UnusedImports
7 | const webpackDevServer = require('webpack-dev-server');
8 |
9 | import { IS_DEV, SERVER_PORT, WEBPACK_PORT } from './server/config';
10 |
11 | const ManifestPlugin = require('webpack-manifest-plugin');
12 |
13 | // Fix for TsconfigPathsPlugin
14 | process.env.TS_NODE_PROJECT = '';
15 | import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
16 |
17 | const plugins = [new ManifestPlugin()];
18 |
19 | const lessToJs = require('less-vars-to-js');
20 | const themeVariables = lessToJs(fs.readFileSync(path.join(__dirname, './ant-theme-vars.less'), 'utf8'));
21 |
22 | // import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
23 | // plugins.push(new BundleAnalyzerPlugin());
24 |
25 | if (IS_DEV) {
26 | plugins.push(new OpenBrowserPlugin({url: `http://localhost:${SERVER_PORT}`}));
27 | }
28 |
29 | const nodeModulesPath = path.resolve(__dirname, 'node_modules');
30 |
31 | const config: webpack.Configuration = {
32 | mode: IS_DEV ? 'development' : 'production',
33 | devtool: IS_DEV ? 'inline-source-map' : false,
34 | entry: ['@babel/polyfill', './client/client'],
35 | output: {
36 | path: path.join(__dirname, 'dist', 'statics'),
37 | filename: `[name]-[hash:8]-bundle.js`,
38 | publicPath: '/statics/',
39 | },
40 | resolve: {
41 | alias: {'react-dom': '@hot-loader/react-dom'},
42 | extensions: ['.js', '.ts', '.tsx'],
43 | plugins: [
44 | new TsconfigPathsPlugin({ configFile: path.join(__dirname, 'client', 'tsconfig.client.json') })
45 | ]
46 | },
47 | optimization: {
48 | splitChunks: {
49 | cacheGroups: {
50 | commons: {
51 | test: /[\\/]node_modules[\\/]/,
52 | name: 'vendors',
53 | chunks: 'all',
54 | },
55 | },
56 | },
57 | },
58 | module: {
59 | rules: [
60 | {
61 | test: /\.tsx?$/,
62 | loaders: ['babel-loader'],
63 | exclude: [/node_modules/, nodeModulesPath],
64 | },
65 | {
66 | test: /\.less$/,
67 | exclude: /node_modules/,
68 | use: [
69 | {
70 | loader: 'style-loader',
71 | },
72 | {
73 | loader: 'css-loader',
74 | options: {
75 | modules: true,
76 | camelCase: true,
77 | sourceMap: IS_DEV,
78 | },
79 | },
80 | {
81 | loader: 'less-loader',
82 | options: {
83 | javascriptEnabled: true,
84 | // modifyVars: themeVariables,
85 | }
86 | },
87 | {
88 | loader: 'postcss-loader',
89 | options: {
90 | sourceMap: IS_DEV,
91 | plugins: IS_DEV ? [cssnano()] : [],
92 | },
93 | },
94 | ],
95 | },
96 | {
97 | test: /node_modules\/.*\.less$/,
98 | use: [
99 | {
100 | loader: 'style-loader',
101 | },
102 | {
103 | loader: 'css-loader',
104 | options: {
105 | sourceMap: true,
106 | },
107 | },
108 | {
109 | loader: 'less-loader',
110 | options: {
111 | javascriptEnabled: true,
112 | sourceMap: true,
113 | // modifyVars: themeVariables,
114 | // root: path.resolve(__dirname, './')
115 | }
116 | },
117 | ]
118 | },
119 | {
120 | test: /.jpe?g$|.gif$|.png$|.svg$|.woff$|.woff2$|.ttf$|.eot$/,
121 | use: 'url-loader?limit=10000',
122 | },
123 | ],
124 | },
125 | devServer: {
126 | port: WEBPACK_PORT,
127 | },
128 | plugins,
129 | externals: {},
130 | };
131 |
132 | export default config;
133 |
--------------------------------------------------------------------------------