Backend part of a social network app which I made as a final project submission for The Odin Project.
Description | Method | URL |
---|---|---|
Register | POST | /api/users/register |
Login | POST | /api/users/login |
Logout from current session | POST | /api/users/logout |
Logout from all sessions | POST | /api/users/logout/all |
Get current user data | GET | /api/users |
Get other user profile | GET | /api/users/:id |
Search users | GET | /api/users/search |
Edit own profile | PATCH | /api/users |
Request friend | PATCH | /api/users/:id/friend/request |
Accept friend | PATCH | /api/users/:id/friend/accept |
Reject friend | PATCH | /api/users/:id/friend/reject |
Remove friend | PATCH | /api/users/:id/friend/remove |
Description | Method | URL |
---|---|---|
Create post | POST | /api/posts |
Get own feed | GET | /api/posts/feed |
Get user timeline | GET | /api/posts/timeline/:userId |
Like post | PATCH | /api/posts/:id/like |
Unlike post | PATCH | /api/posts/:id/unlike |
Description | Method | URL |
---|---|---|
Create comment | POST | /api/comments/:postId |
Get post comments | GET | /api/comments/:postId |
git clone https://github.com/michalosman/social-network-api.git
cd social-network-api
PORT=<The port the server will run on, e.g. 5000>
CLIENT_URL=<Address of the client, e.g. https://localhost:3000/>
MONGO_URI_PROD=<URI used to connect to a production MongoDB database>
MONGO_URI_DEV=<URI used to connect to a development MongoDB database>
SESSION_SECRET=<Secret used to sign the session ID cookie>
ACCESS_TOKEN_PUBLIC_KEY=<Public access token RSA key>
ACCESS_TOKEN_PRIVATE_KEY=<Private access token RSA key>
REFRESH_TOKEN_PUBLIC_KEY=<Public refresh token RSA key>
REFRESH_TOKEN_PRIVATE_KEY=<Private refresh token RSA key>
MY_USER_ID_PROD=<Your user ID in production environment (optional)>
TEST_USER_ID_PROD=<Test user ID in production environment (optional)>
MY_USER_ID_DEV=<Your user ID in development environment (optional)>
TEST_USER_ID_DEV=<Test user ID in development environment (optional)>
npm i
npm start
Makes throwing errors more intuitive and explicit
// Before
if (doesExist) {
throw new Error('User already exists')
}
// After
if (doesExist) {
throw new Conflict('User already exists')
}
Handles all errors in one place
// Before
try {
const user = await UserService.register(req.body)
res.json({ data: user })
} catch (error) {
res.json({
error: {
code: error.code,
message: error.message,
},
})
}
// After
try {
const user = await UserService.register(req.body)
res.json({ data: user })
} catch (error) {
next(error)
}
Passes thrown errors straight to error handler
// Before
try {
const user = await UserService.register(req.body) // this will throw
res.json({ data: user })
} catch (error) {
next(error)
}
// After
const user = await UserService.register(req.body) // this will throw
res.json({ data: user })
For users authentication I used JWT tokens which are stored on client side as cookies (accessToken & refreshToken). The access token expires every 5 minutes and is used for short term authentication. The refresh token expires every year and is used to refresh access tokens.
Refresh tokens are stored in database and are verified on each access token refresh. If refresh token gets stolen user can use "Logout from all sessions" option to remove all tokens from database so they can't be used to refresh the access token anymore.
The diagram below represents the flow of processing authenticated requests.